mirror of
https://github.com/standardnotes/server
synced 2026-03-22 00:01:13 -04:00
Compare commits
154 Commits
@standardn
...
settings_s
| 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 |
116
.github/workflows/common-server-application.yml
vendored
116
.github/workflows/common-server-application.yml
vendored
@@ -130,77 +130,77 @@ jobs:
|
||||
- name: Test
|
||||
run: yarn test ${{ inputs.package_path }}
|
||||
|
||||
# e2e:
|
||||
# runs-on: ubuntu-latest
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# needs: build
|
||||
needs: build
|
||||
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# - name: Cache build
|
||||
# id: cache-build
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: |
|
||||
# packages/**/dist
|
||||
# ${{ needs.build.outputs.temp_dir }}
|
||||
# key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
|
||||
- name: Cache build
|
||||
id: cache-build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
packages/**/dist
|
||||
${{ needs.build.outputs.temp_dir }}
|
||||
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
|
||||
|
||||
# - name: Set up Node
|
||||
# uses: actions/setup-node@v3
|
||||
# with:
|
||||
# registry-url: 'https://registry.npmjs.org'
|
||||
# node-version-file: '.nvmrc'
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
# - name: Build
|
||||
# if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
# run: yarn build ${{ inputs.package_path }}
|
||||
- name: Build
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
run: yarn build ${{ inputs.package_path }}
|
||||
|
||||
# - name: Bundle
|
||||
# if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
# run: yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory ${{ needs.build.outputs.temp_dir }}
|
||||
- name: Bundle
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
run: yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory ${{ needs.build.outputs.temp_dir }}
|
||||
|
||||
# - name: Login to Docker Hub
|
||||
# uses: docker/login-action@v2
|
||||
# with:
|
||||
# username: ${{ secrets.DOCKER_USERNAME }}
|
||||
# password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
# - name: Set up QEMU
|
||||
# uses: docker/setup-qemu-action@master
|
||||
# with:
|
||||
# platforms: all
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# id: buildx
|
||||
# uses: docker/setup-buildx-action@master
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
|
||||
# - name: Publish Docker image for E2E testing
|
||||
# uses: docker/build-push-action@v3
|
||||
# with:
|
||||
# builder: ${{ steps.buildx.outputs.name }}
|
||||
# context: ${{ needs.build.outputs.temp_dir }}
|
||||
# file: ${{ needs.build.outputs.temp_dir }}/${{ inputs.package_path }}/Dockerfile
|
||||
# platforms: linux/amd64,linux/arm64
|
||||
# push: true
|
||||
# tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
|
||||
- name: Publish Docker image for E2E testing
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: ${{ needs.build.outputs.temp_dir }}
|
||||
file: ${{ needs.build.outputs.temp_dir }}/${{ inputs.package_path }}/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
|
||||
|
||||
# - name: Run E2E test suite
|
||||
# uses: convictional/trigger-workflow-and-wait@v1.6.3
|
||||
# with:
|
||||
# owner: standardnotes
|
||||
# repo: e2e
|
||||
# github_token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
# workflow_file_name: testing-with-stable-client.yml
|
||||
# wait_interval: 30
|
||||
# client_payload: '{"${{ inputs.e2e_tag_parameter_name }}": "${{ github.sha }}"}'
|
||||
# propagate_failure: true
|
||||
# trigger_workflow: true
|
||||
# wait_workflow: true
|
||||
- name: Run E2E test suite
|
||||
uses: convictional/trigger-workflow-and-wait@master
|
||||
with:
|
||||
owner: standardnotes
|
||||
repo: e2e
|
||||
github_token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
workflow_file_name: testing-with-stable-client.yml
|
||||
wait_interval: 30
|
||||
client_payload: '{"${{ inputs.e2e_tag_parameter_name }}": "${{ github.sha }}"}'
|
||||
propagate_failure: true
|
||||
trigger_workflow: true
|
||||
wait_workflow: true
|
||||
|
||||
publish:
|
||||
needs: [ build, test, lint ]
|
||||
needs: [ build, test, lint, e2e ]
|
||||
|
||||
name: Publish Docker Image
|
||||
uses: standardnotes/server/.github/workflows/common-docker-image.yml@main
|
||||
|
||||
176
.pnp.cjs
generated
176
.pnp.cjs
generated
@@ -66,7 +66,7 @@ const RAW_RUNTIME_STATE =
|
||||
"reference": "workspace:packages/security"\
|
||||
},\
|
||||
{\
|
||||
"name": "@standardnotes/settings",\
|
||||
"name": "@standardnotes/settings-server",\
|
||||
"reference": "workspace:packages/settings"\
|
||||
},\
|
||||
{\
|
||||
@@ -107,7 +107,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/scheduler-server", ["workspace:packages/scheduler"]],\
|
||||
["@standardnotes/security", ["workspace:packages/security"]],\
|
||||
["@standardnotes/server-monorepo", ["workspace:."]],\
|
||||
["@standardnotes/settings", ["workspace:packages/settings"]],\
|
||||
["@standardnotes/settings-server", ["workspace:packages/settings"]],\
|
||||
["@standardnotes/sncrypto-node", ["workspace:packages/sncrypto-node"]],\
|
||||
["@standardnotes/syncing-server", ["workspace:packages/syncing-server"]],\
|
||||
["@standardnotes/time", ["workspace:packages/time"]],\
|
||||
@@ -126,7 +126,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@lerna-lite/cli", "npm:1.6.0"],\
|
||||
["@lerna-lite/list", "npm:1.6.0"],\
|
||||
["@lerna-lite/run", "npm:1.6.0"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
@@ -2538,7 +2537,6 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/analytics/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
@@ -2551,7 +2549,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@types/node", "npm:18.11.9"],\
|
||||
["@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"],\
|
||||
["dotenv", "npm:16.0.1"],\
|
||||
["eslint", "npm:8.25.0"],\
|
||||
@@ -2584,6 +2582,20 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"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", [\
|
||||
@@ -2591,7 +2603,6 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/api-gateway/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
@@ -2607,7 +2618,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@types/prettyjson", "npm:0.0.30"],\
|
||||
["@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"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dotenv", "npm:16.0.1"],\
|
||||
@@ -2648,18 +2659,17 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/auth/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/auth-server", "workspace:packages/auth"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/features", "npm:1.53.1"],\
|
||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||
["@standardnotes/responses", "npm:1.11.1"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
|
||||
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
@@ -2674,7 +2684,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/ua-parser-js", "npm:0.7.36"],\
|
||||
["@types/uuid", "npm:8.3.4"],\
|
||||
["@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"],\
|
||||
["bcryptjs", "npm:2.4.3"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
@@ -2783,13 +2793,12 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/domain-events-infra/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@types/ioredis", "npm:5.0.0"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@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"],\
|
||||
["ioredis", "npm:5.2.4"],\
|
||||
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.2"],\
|
||||
@@ -2816,6 +2825,19 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"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", [\
|
||||
@@ -2823,7 +2845,6 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/event-store/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/event-store", "workspace:packages/event-store"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
@@ -2832,7 +2853,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@types/nodemailer", "npm:6.4.6"],\
|
||||
["@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"],\
|
||||
["eslint", "npm:8.25.0"],\
|
||||
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
|
||||
@@ -2872,6 +2893,17 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"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", [\
|
||||
@@ -2879,7 +2911,6 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/files/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/files-server", "workspace:packages/files"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/config", "npm:2.4.3"],\
|
||||
@@ -2899,7 +2930,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/prettyjson", "npm:0.0.30"],\
|
||||
["@types/uuid", "npm:8.3.4"],\
|
||||
["@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"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dayjs", "npm:1.11.6"],\
|
||||
@@ -2954,6 +2985,13 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"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", [\
|
||||
@@ -3007,6 +3045,17 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"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", [\
|
||||
@@ -3014,7 +3063,6 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/revisions/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/revisions-server", "workspace:packages/revisions"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
@@ -3032,7 +3080,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@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.1253.0"],\
|
||||
["aws-sdk", "npm:2.1260.0"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dotenv", "npm:16.0.1"],\
|
||||
["eslint", "npm:8.25.0"],\
|
||||
@@ -3060,10 +3108,10 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/scheduler/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/scheduler-server", "workspace:packages/scheduler"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||
@@ -3073,7 +3121,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@types/node", "npm:18.11.9"],\
|
||||
["@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"],\
|
||||
["dotenv", "npm:16.0.1"],\
|
||||
["eslint", "npm:8.25.0"],\
|
||||
@@ -3122,7 +3170,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@lerna-lite/cli", "npm:1.6.0"],\
|
||||
["@lerna-lite/list", "npm:1.6.0"],\
|
||||
["@lerna-lite/run", "npm:1.6.0"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
@@ -3140,16 +3187,46 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "SOFT"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/settings", [\
|
||||
["@standardnotes/settings-server", [\
|
||||
["workspace:packages/settings", {\
|
||||
"packageLocation": "./packages/settings/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
|
||||
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:4.2.1"],\
|
||||
["@standardnotes/settings-server", "workspace:packages/settings"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@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"],\
|
||||
["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"\
|
||||
}]\
|
||||
@@ -3162,6 +3239,14 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"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", [\
|
||||
@@ -3189,7 +3274,6 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/syncing-server/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
@@ -3199,7 +3283,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/payloads", "npm:1.5.1"],\
|
||||
["@standardnotes/responses", "npm:1.11.1"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
["@types/cors", "npm:2.8.12"],\
|
||||
["@types/dotenv", "npm:8.2.0"],\
|
||||
@@ -3213,7 +3296,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/ua-parser-js", "npm:0.7.36"],\
|
||||
["@types/uuid", "npm:8.3.4"],\
|
||||
["@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"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dotenv", "npm:16.0.1"],\
|
||||
@@ -3283,6 +3366,17 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"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", [\
|
||||
@@ -3290,7 +3384,6 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/websockets/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
@@ -3304,7 +3397,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@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.1253.0"],\
|
||||
["aws-sdk", "npm:2.1260.0"],\
|
||||
["axios", "npm:1.1.3"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dotenv", "npm:16.0.1"],\
|
||||
@@ -3331,11 +3424,11 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/workspace/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/workspace-server", "workspace:packages/workspace"],\
|
||||
["@newrelic/native-metrics", "npm:9.0.0"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/models", "npm:1.28.0"],\
|
||||
@@ -3347,7 +3440,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@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.1253.0"],\
|
||||
["aws-sdk", "npm:2.1260.0"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dotenv", "npm:16.0.1"],\
|
||||
["eslint", "npm:8.25.0"],\
|
||||
@@ -4814,10 +4907,10 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["aws-sdk", [\
|
||||
["npm:2.1253.0", {\
|
||||
"packageLocation": "./.yarn/cache/aws-sdk-npm-2.1253.0-2cf60975ab-faa4af2949.zip/node_modules/aws-sdk/",\
|
||||
["npm:2.1260.0", {\
|
||||
"packageLocation": "./.yarn/cache/aws-sdk-npm-2.1260.0-0145998ab1-9a1b2e4cb5.zip/node_modules/aws-sdk/",\
|
||||
"packageDependencies": [\
|
||||
["aws-sdk", "npm:2.1253.0"],\
|
||||
["aws-sdk", "npm:2.1260.0"],\
|
||||
["buffer", "npm:4.9.2"],\
|
||||
["events", "npm:1.1.1"],\
|
||||
["ieee754", "npm:1.1.13"],\
|
||||
@@ -6382,6 +6475,13 @@ const RAW_RUNTIME_STATE =
|
||||
["dompurify", "npm:2.4.0"]\
|
||||
],\
|
||||
"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", [\
|
||||
@@ -12584,7 +12684,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["sqs-consumer", "virtual:685a6222c3349423674bb7f0684ba34e2ab20912010f352e04dcf707a156e13183fc382e2417cb37a60f3e7b52fd0178c53181674890e1773eb83e190dc13378#npm:5.7.0"],\
|
||||
["@types/aws-sdk", null],\
|
||||
["aws-sdk", "npm:2.1253.0"],\
|
||||
["aws-sdk", "npm:2.1260.0"],\
|
||||
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"]\
|
||||
],\
|
||||
"packagePeers": [\
|
||||
|
||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120,
|
||||
"semi": false
|
||||
}
|
||||
BIN
.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip
vendored
Normal file
BIN
.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip
vendored
Normal file
Binary file not shown.
@@ -61,7 +61,6 @@
|
||||
},
|
||||
"packageManager": "yarn@4.0.0-rc.25",
|
||||
"dependencies": {
|
||||
"@newrelic/native-metrics": "^9.0.0",
|
||||
"@sentry/node": "^7.19.0",
|
||||
"newrelic": "^9.6.0"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,164 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'newrelic'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { EmailLevel } from '@standardnotes/domain-core'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
||||
import { Period } from '../src/Domain/Time/Period'
|
||||
@@ -16,6 +17,8 @@ import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
const requestReport = async (
|
||||
analyticsStore: AnalyticsStoreInterface,
|
||||
@@ -24,6 +27,8 @@ const requestReport = async (
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
|
||||
timer: TimerInterface,
|
||||
adminEmails: string[],
|
||||
): Promise<void> => {
|
||||
await calculateMonthlyRecurringRevenue.execute({})
|
||||
|
||||
@@ -213,18 +218,29 @@ const requestReport = async (
|
||||
})
|
||||
}
|
||||
|
||||
const event = domainEventFactory.createDailyAnalyticsReportGeneratedEvent({
|
||||
activityStatistics: yesterdayActivityStatistics,
|
||||
activityStatisticsOverTime: analyticsOverTime,
|
||||
statisticsOverTime,
|
||||
statisticMeasures,
|
||||
churn: {
|
||||
periodKeys: monthlyPeriodKeys,
|
||||
values: churnRates,
|
||||
},
|
||||
})
|
||||
|
||||
await domainEventPublisher.publish(event)
|
||||
for (const adminEmail of adminEmails) {
|
||||
await domainEventPublisher.publish(
|
||||
domainEventFactory.createEmailRequestedEvent({
|
||||
messageIdentifier: 'VERSION_ADOPTION_REPORT',
|
||||
subject: getSubject(),
|
||||
body: getBody(
|
||||
{
|
||||
activityStatistics: yesterdayActivityStatistics,
|
||||
activityStatisticsOverTime: analyticsOverTime,
|
||||
statisticsOverTime,
|
||||
statisticMeasures,
|
||||
churn: {
|
||||
periodKeys: monthlyPeriodKeys,
|
||||
values: churnRates,
|
||||
},
|
||||
},
|
||||
timer,
|
||||
),
|
||||
level: EmailLevel.LEVELS.System,
|
||||
userEmail: adminEmail,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
@@ -241,9 +257,13 @@ void container.load().then((container) => {
|
||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
||||
const timer: TimerInterface = container.get(TYPES.Timer)
|
||||
const calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue = container.get(
|
||||
TYPES.CalculateMonthlyRecurringRevenue,
|
||||
)
|
||||
const adminEmails = container.get(TYPES.ADMIN_EMAILS) as string[]
|
||||
|
||||
logger.info(`Sending report to following admins: ${adminEmails}`)
|
||||
|
||||
Promise.resolve(
|
||||
requestReport(
|
||||
@@ -253,6 +273,8 @@ void container.load().then((container) => {
|
||||
domainEventPublisher,
|
||||
periodKeyGenerator,
|
||||
calculateMonthlyRecurringRevenue,
|
||||
timer,
|
||||
adminEmails,
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
|
||||
@@ -5,17 +5,17 @@ COMMAND=$1 && shift 1
|
||||
|
||||
case "$COMMAND" in
|
||||
'start-worker' )
|
||||
echo "Starting Worker..."
|
||||
echo "[Docker] Starting Worker..."
|
||||
yarn workspace @standardnotes/analytics worker
|
||||
;;
|
||||
|
||||
'report' )
|
||||
echo "Starting Usage Report Generation..."
|
||||
echo "[Docker] Starting Usage Report Generation..."
|
||||
yarn workspace @standardnotes/analytics report
|
||||
;;
|
||||
|
||||
* )
|
||||
echo "Unknown command"
|
||||
echo "[Docker] Unknown command"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@ module.exports = {
|
||||
transform: {
|
||||
...tsjPreset.transform,
|
||||
},
|
||||
coveragePathIgnorePatterns: ['/Infra/'],
|
||||
coveragePathIgnorePatterns: ['/Infra/', '/Domain/Email/'],
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.11.6",
|
||||
"version": "2.12.25",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -37,15 +37,14 @@
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/native-metrics": "^9.0.0",
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.19.0",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
"@standardnotes/time": "workspace:*",
|
||||
"aws-sdk": "^2.1253.0",
|
||||
"aws-sdk": "^2.1260.0",
|
||||
"dayjs": "^1.11.6",
|
||||
"dotenv": "^16.0.1",
|
||||
"inversify": "^6.0.1",
|
||||
|
||||
@@ -89,13 +89,24 @@ export class ContainerConfigLoader {
|
||||
})
|
||||
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
||||
|
||||
if (env.get('SNS_AWS_REGION', true)) {
|
||||
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(
|
||||
new AWS.SNS({
|
||||
apiVersion: 'latest',
|
||||
region: env.get('SNS_AWS_REGION', true),
|
||||
}),
|
||||
)
|
||||
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||
const snsConfig: AWS.SNS.Types.ClientConfiguration = {
|
||||
apiVersion: 'latest',
|
||||
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)) {
|
||||
@@ -119,6 +130,7 @@ export class ContainerConfigLoader {
|
||||
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.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||
container.bind(TYPES.ADMIN_EMAILS).toConstantValue(env.get('ADMIN_EMAILS').split(','))
|
||||
|
||||
// Repositories
|
||||
container
|
||||
|
||||
@@ -11,6 +11,7 @@ const TYPES = {
|
||||
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
||||
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||
ADMIN_EMAILS: Symbol.for('ADMIN_EMAILS'),
|
||||
// Repositories
|
||||
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
||||
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
||||
|
||||
11
packages/analytics/src/Domain/Email/DailyAnalyticsReport.ts
Normal file
11
packages/analytics/src/Domain/Email/DailyAnalyticsReport.ts
Normal file
@@ -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,
|
||||
})
|
||||
@Index('email')
|
||||
declare userEmail: string
|
||||
declare username: string
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
import { DomainEventService, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
||||
import { DomainEventService, EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
@@ -9,55 +9,20 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
@injectable()
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
||||
|
||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
||||
activityStatistics: Array<{
|
||||
name: string
|
||||
retention: number
|
||||
totalCount: number
|
||||
}>
|
||||
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 {
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: string
|
||||
level: string
|
||||
body: string
|
||||
subject: string
|
||||
}): EmailRequestedEvent {
|
||||
return {
|
||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
||||
type: 'EMAIL_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: '',
|
||||
userIdentifierType: 'uuid',
|
||||
userIdentifier: dto.userEmail,
|
||||
userIdentifierType: 'email',
|
||||
},
|
||||
origin: DomainEventService.Analytics,
|
||||
},
|
||||
|
||||
@@ -1,45 +1,11 @@
|
||||
import { DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
||||
import { EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||
|
||||
export interface DomainEventFactoryInterface {
|
||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
||||
activityStatistics: Array<{
|
||||
name: string
|
||||
retention: number
|
||||
totalCount: number
|
||||
}>
|
||||
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
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: string
|
||||
level: string
|
||||
body: string
|
||||
subject: string
|
||||
}): EmailRequestedEvent
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Email } from '@standardnotes/domain-core'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
@@ -41,7 +41,7 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
payedAmount: event.payload.payAmount,
|
||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||
subscriptionId: event.payload.subscriptionId,
|
||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
||||
username: Username.create(event.payload.userEmail).getValue(),
|
||||
userUuid,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { DomainEventHandlerInterface, SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Email } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
@@ -45,7 +45,7 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
||||
payedAmount: event.payload.payAmount,
|
||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||
subscriptionId: event.payload.subscriptionId,
|
||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
||||
username: Username.create(event.payload.userEmail).getValue(),
|
||||
userUuid,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { DomainEventHandlerInterface, SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Email } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
@@ -69,7 +69,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
payedAmount: event.payload.payAmount,
|
||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||
subscriptionId: event.payload.subscriptionId,
|
||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
||||
username: Username.create(event.payload.userEmail).getValue(),
|
||||
userUuid,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { DomainEventHandlerInterface, SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Email } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
@@ -41,7 +41,7 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
payedAmount: event.payload.payAmount,
|
||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||
subscriptionId: event.payload.subscriptionId,
|
||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
||||
username: Username.create(event.payload.userEmail).getValue(),
|
||||
userUuid,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Email } from '@standardnotes/domain-core'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
@@ -41,7 +41,7 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
||||
payedAmount: event.payload.payAmount,
|
||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||
subscriptionId: event.payload.subscriptionId,
|
||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
||||
username: Username.create(event.payload.userEmail).getValue(),
|
||||
userUuid,
|
||||
})
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
||||
async handle(event: UserRegisteredEvent): Promise<void> {
|
||||
let analyticsEntity = new AnalyticsEntity()
|
||||
analyticsEntity.userUuid = event.payload.userUuid
|
||||
analyticsEntity.userEmail = event.payload.email
|
||||
analyticsEntity.username = event.payload.email
|
||||
analyticsEntity = await this.analyticsEntityRepository.save(analyticsEntity)
|
||||
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsEntity.id, [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { MonthlyRevenue } from '../Revenue/MonthlyRevenue'
|
||||
@@ -14,7 +14,7 @@ export class RevenueModificationMap implements MapperInterface<RevenueModificati
|
||||
toDomain(persistence: TypeORMRevenueModification): RevenueModification {
|
||||
const userOrError = User.create(
|
||||
{
|
||||
email: Email.create(persistence.userEmail).getValue(),
|
||||
username: Username.create(persistence.username).getValue(),
|
||||
},
|
||||
new UniqueEntityId(persistence.userUuid),
|
||||
)
|
||||
@@ -70,7 +70,7 @@ export class RevenueModificationMap implements MapperInterface<RevenueModificati
|
||||
persistence.previousMonthlyRevenue = domain.props.previousMonthlyRevenue.value
|
||||
persistence.subscriptionId = subscription.id.toValue() as number
|
||||
persistence.subscriptionPlan = subscription.props.planName.value
|
||||
persistence.userEmail = user.props.email.value
|
||||
persistence.username = user.props.username.value
|
||||
persistence.userUuid = user.id.toString()
|
||||
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 { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||
@@ -19,7 +19,7 @@ describe('RevenueModification', () => {
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
}).getValue()
|
||||
user = User.create({
|
||||
email: Email.create('test@test.te').getValue(),
|
||||
username: Username.create('test@test.te').getValue(),
|
||||
}).getValue()
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('GetUserAnalyticsId', () => {
|
||||
analyticsEntity = {
|
||||
id: 123,
|
||||
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
|
||||
userEmail: 'test@test.te',
|
||||
username: 'test@test.te',
|
||||
} as jest.Mocked<AnalyticsEntity>
|
||||
|
||||
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
|
||||
@@ -28,7 +28,7 @@ export class GetUserAnalyticsId implements UseCaseInterface {
|
||||
return {
|
||||
analyticsId: analyticsEntity.id,
|
||||
userUuid: Uuid.create(analyticsEntity.userUuid).getValue(),
|
||||
userEmail: Email.create(analyticsEntity.userEmail).getValue(),
|
||||
username: Username.create(analyticsEntity.username).getValue(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Email, Uuid } from '@standardnotes/domain-core'
|
||||
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export type GetUserAnalyticsIdResponse = {
|
||||
analyticsId: number
|
||||
userEmail: Email
|
||||
username: Username
|
||||
userUuid: Uuid
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
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'
|
||||
|
||||
@@ -45,7 +45,7 @@ describe('SaveRevenueModification', () => {
|
||||
payedAmount: 12.99,
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
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(),
|
||||
})
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('SaveRevenueModification', () => {
|
||||
payedAmount: 12.99,
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
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(),
|
||||
})
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('SaveRevenueModification', () => {
|
||||
payedAmount: 2,
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
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(),
|
||||
})
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('SaveRevenueModification', () => {
|
||||
payedAmount: 12.99,
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
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(),
|
||||
})
|
||||
|
||||
@@ -122,7 +122,7 @@ describe('SaveRevenueModification', () => {
|
||||
payedAmount: 12.99,
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
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(),
|
||||
})
|
||||
|
||||
@@ -142,7 +142,7 @@ describe('SaveRevenueModification', () => {
|
||||
payedAmount: 12.99,
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
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(),
|
||||
})
|
||||
|
||||
@@ -162,7 +162,7 @@ describe('SaveRevenueModification', () => {
|
||||
payedAmount: 12.99,
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
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(),
|
||||
})
|
||||
|
||||
@@ -182,7 +182,7 @@ describe('SaveRevenueModification', () => {
|
||||
payedAmount: 12.99,
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
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(),
|
||||
})
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('SaveRevenueModification', () => {
|
||||
payedAmount: 12.99,
|
||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||
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(),
|
||||
})
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export class SaveRevenueModification implements DomainUseCaseInterface<RevenueMo
|
||||
async execute(dto: SaveRevenueModificationDTO): Promise<Result<RevenueModification>> {
|
||||
const userOrError = User.create(
|
||||
{
|
||||
email: dto.userEmail,
|
||||
username: dto.username,
|
||||
},
|
||||
new UniqueEntityId(dto.userUuid.value),
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Email, Uuid } from '@standardnotes/domain-core'
|
||||
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
|
||||
import { SubscriptionPlanName } from '../../Subscription/SubscriptionPlanName'
|
||||
@@ -9,7 +9,7 @@ export interface SaveRevenueModificationDTO {
|
||||
planName: SubscriptionPlanName
|
||||
newSubscriber: boolean
|
||||
userUuid: Uuid
|
||||
userEmail: Email
|
||||
username: Username
|
||||
subscriptionId: number
|
||||
billingFrequency: number
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Email } from '@standardnotes/domain-core'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
|
||||
import { User } from './User'
|
||||
|
||||
describe('User', () => {
|
||||
it('should create an entity', () => {
|
||||
const user = User.create({
|
||||
email: Email.create('test@test.te').getValue(),
|
||||
username: Username.create('test@test.te').getValue(),
|
||||
}).getValue()
|
||||
|
||||
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 {
|
||||
email: Email
|
||||
username: Username
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export class TypeORMRevenueModification {
|
||||
length: 255,
|
||||
})
|
||||
@Index('email')
|
||||
declare userEmail: string
|
||||
declare username: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
|
||||
@@ -11,6 +11,7 @@ WEB_SOCKET_SERVER_URL=http://websockets:3000
|
||||
PAYMENTS_SERVER_URL=http://payments:3000
|
||||
FILES_SERVER_URL=http://files:3000
|
||||
REVISIONS_SERVER_URL=http://revisions:3000
|
||||
EMAIL_SERVER_URL=http://email:3000
|
||||
|
||||
HTTP_CALL_TIMEOUT=60000
|
||||
|
||||
|
||||
@@ -3,6 +3,118 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.39.0",
|
||||
"version": "1.40.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -20,7 +20,6 @@
|
||||
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/native-metrics": "^9.0.0",
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.19.0",
|
||||
"@standardnotes/common": "workspace:^",
|
||||
@@ -28,7 +27,7 @@
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
"@standardnotes/security": "workspace:*",
|
||||
"@standardnotes/time": "workspace:*",
|
||||
"aws-sdk": "^2.1253.0",
|
||||
"aws-sdk": "^2.1260.0",
|
||||
"axios": "^1.1.3",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "^16.0.1",
|
||||
|
||||
@@ -54,11 +54,12 @@ export class ContainerConfigLoader {
|
||||
// env vars
|
||||
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.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.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.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.HTTP_CALL_TIMEOUT)
|
||||
|
||||
@@ -8,6 +8,7 @@ const TYPES = {
|
||||
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
|
||||
FILES_SERVER_URL: Symbol.for('FILES_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'),
|
||||
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
|
||||
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> {
|
||||
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.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: 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.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@@ -39,6 +40,11 @@ export class HttpService implements HttpServiceInterface {
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
if (!this.revisionsServerUrl) {
|
||||
response.status(400).send({ message: 'Revisions Server not configured' })
|
||||
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
if (!this.workspaceServerUrl) {
|
||||
response.status(400).send({ message: 'Workspace Server not configured' })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await this.callServer(this.workspaceServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
export interface HttpServiceInterface {
|
||||
callEmailServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
callAuthServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
|
||||
@@ -3,6 +3,182 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -3,20 +3,19 @@ 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 { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||
import { MuteFailedBackupsEmailsOption, MuteFailedCloudBackupsEmailsOption, SettingName } from '@standardnotes/settings'
|
||||
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { SettingServiceInterface } from '../src/Domain/Setting/SettingServiceInterface'
|
||||
|
||||
const inputArgs = process.argv.slice(2)
|
||||
@@ -30,38 +29,38 @@ const requestBackups = async (
|
||||
domainEventFactory: DomainEventFactoryInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
): Promise<void> => {
|
||||
let settingName: SettingName,
|
||||
let settingName: string,
|
||||
permissionName: PermissionName,
|
||||
muteEmailsSettingName: SettingName,
|
||||
muteEmailsSettingName: string,
|
||||
muteEmailsSettingValue: string,
|
||||
providerTokenSettingName: SettingName
|
||||
providerTokenSettingName: string
|
||||
switch (backupProvider) {
|
||||
case 'email':
|
||||
settingName = SettingName.EmailBackupFrequency
|
||||
settingName = SettingName.NAMES.EmailBackupFrequency
|
||||
permissionName = PermissionName.DailyEmailBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||
muteEmailsSettingValue = 'muted'
|
||||
break
|
||||
case 'dropbox':
|
||||
settingName = SettingName.DropboxBackupFrequency
|
||||
settingName = SettingName.NAMES.DropboxBackupFrequency
|
||||
permissionName = PermissionName.DailyDropboxBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
||||
providerTokenSettingName = SettingName.DropboxBackupToken
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = 'muted'
|
||||
providerTokenSettingName = SettingName.NAMES.DropboxBackupToken
|
||||
break
|
||||
case 'one_drive':
|
||||
settingName = SettingName.OneDriveBackupFrequency
|
||||
settingName = SettingName.NAMES.OneDriveBackupFrequency
|
||||
permissionName = PermissionName.DailyOneDriveBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
||||
providerTokenSettingName = SettingName.OneDriveBackupToken
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = 'muted'
|
||||
providerTokenSettingName = SettingName.NAMES.OneDriveBackupToken
|
||||
break
|
||||
case 'google_drive':
|
||||
settingName = SettingName.GoogleDriveBackupFrequency
|
||||
settingName = SettingName.NAMES.GoogleDriveBackupFrequency
|
||||
permissionName = PermissionName.DailyGDriveBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
||||
providerTokenSettingName = SettingName.GoogleDriveBackupToken
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = 'muted'
|
||||
providerTokenSettingName = SettingName.NAMES.GoogleDriveBackupToken
|
||||
break
|
||||
default:
|
||||
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 { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
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 { PermissionName } from '@standardnotes/features'
|
||||
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
||||
@@ -28,8 +28,8 @@ const requestBackups = async (
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
): Promise<void> => {
|
||||
const permissionName = PermissionName.DailyEmailBackup
|
||||
const muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
|
||||
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
||||
const muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||
const muteEmailsSettingValue = 'muted'
|
||||
|
||||
if (!backupEmail) {
|
||||
throw new Error('Could not trigger email backup for user - missing email parameter')
|
||||
|
||||
@@ -5,64 +5,58 @@ COMMAND=$1 && shift 1
|
||||
|
||||
case "$COMMAND" in
|
||||
'start-local' )
|
||||
echo "Starting Web..."
|
||||
echo "[Docker] Starting Web..."
|
||||
yarn workspace @standardnotes/auth-server start:local
|
||||
;;
|
||||
|
||||
'start-web' )
|
||||
echo "Starting Web..."
|
||||
echo "[Docker] Starting Web..."
|
||||
yarn workspace @standardnotes/auth-server start
|
||||
;;
|
||||
|
||||
'start-worker' )
|
||||
echo "Starting Worker..."
|
||||
echo "[Docker] Starting Worker..."
|
||||
yarn workspace @standardnotes/auth-server worker
|
||||
;;
|
||||
|
||||
'email-daily-backup' )
|
||||
echo "Starting Email Daily Backup..."
|
||||
echo "[Docker] Starting Email Daily Backup..."
|
||||
yarn workspace @standardnotes/auth-server daily-backup:email
|
||||
;;
|
||||
|
||||
'email-weekly-backup' )
|
||||
echo "Starting Email Weekly Backup..."
|
||||
echo "[Docker] Starting Email Weekly Backup..."
|
||||
yarn workspace @standardnotes/auth-server weekly-backup:email
|
||||
;;
|
||||
|
||||
'email-backup' )
|
||||
echo "Starting Email Backup For Single User..."
|
||||
echo "[Docker] Starting Email Backup For Single User..."
|
||||
EMAIL=$1 && shift 1
|
||||
yarn workspace @standardnotes/auth-server user-email-backup $EMAIL
|
||||
;;
|
||||
|
||||
'dropbox-daily-backup' )
|
||||
echo "Starting Dropbox Daily Backup..."
|
||||
echo "[Docker] Starting Dropbox Daily Backup..."
|
||||
yarn workspace @standardnotes/auth-server daily-backup:dropbox
|
||||
;;
|
||||
|
||||
'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
|
||||
;;
|
||||
|
||||
'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
|
||||
;;
|
||||
|
||||
'email-campaign' )
|
||||
echo "Starting Email Campaign Sending..."
|
||||
MESSAGE_IDENTIFIER=$1 && shift 1
|
||||
yarn workspace @standardnotes/auth-server email-campaign $MESSAGE_IDENTIFIER
|
||||
;;
|
||||
|
||||
'content-recalculation' )
|
||||
echo "Starting Content Size Recalculation..."
|
||||
echo "[Docker] Starting Content Size Recalculation..."
|
||||
yarn workspace @standardnotes/auth-server content-recalculation
|
||||
;;
|
||||
|
||||
* )
|
||||
echo "Unknown command"
|
||||
echo "[Docker] Unknown command"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@ module.exports = {
|
||||
transform: {
|
||||
...tsjPreset.transform,
|
||||
},
|
||||
coveragePathIgnorePatterns: ['/Bootstrap/', '/InversifyExpressUtils/', 'HealthCheckController'],
|
||||
coveragePathIgnorePatterns: ['/Bootstrap/', '/InversifyExpressUtils/', '/Infra/', '/Projection/', '/Domain/Email/'],
|
||||
setupFilesAfterEnv: ['./test-setup.ts'],
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Redis, { Cluster } from 'ioredis'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
import { Setting } from '../src/Domain/Setting/Setting'
|
||||
@@ -34,7 +34,7 @@ export class moveMfaItemsToUserSettings1627638504691 implements MigrationInterfa
|
||||
|
||||
const setting = new Setting()
|
||||
setting.uuid = item['uuid']
|
||||
setting.name = SettingName.MfaSecret
|
||||
setting.name = SettingName.NAMES.MfaSecret
|
||||
setting.value = item['content']
|
||||
if (item['deleted']) {
|
||||
setting.value = null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.60.7",
|
||||
"version": "1.67.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -26,27 +26,25 @@
|
||||
"daily-backup:one_drive": "yarn node dist/bin/backup.js one_drive daily",
|
||||
"weekly-backup:email": "yarn node dist/bin/backup.js email weekly",
|
||||
"content-recalculation": "yarn node dist/bin/content.js",
|
||||
"email-campaign": "yarn node dist/bin/email.js",
|
||||
"typeorm": "typeorm-ts-node-commonjs",
|
||||
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/native-metrics": "^9.0.0",
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.19.0",
|
||||
"@standardnotes/api": "^1.19.0",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
"@standardnotes/features": "^1.52.1",
|
||||
"@standardnotes/predicates": "workspace:*",
|
||||
"@standardnotes/responses": "^1.6.39",
|
||||
"@standardnotes/security": "workspace:*",
|
||||
"@standardnotes/settings": "workspace:*",
|
||||
"@standardnotes/sncrypto-common": "^1.9.0",
|
||||
"@standardnotes/sncrypto-node": "workspace:*",
|
||||
"@standardnotes/time": "workspace:*",
|
||||
"aws-sdk": "^2.1253.0",
|
||||
"aws-sdk": "^2.1260.0",
|
||||
"axios": "^1.1.3",
|
||||
"bcryptjs": "2.4.3",
|
||||
"cors": "2.8.5",
|
||||
|
||||
@@ -193,6 +193,7 @@ import { SubscriptionInvitesController } from '../Controller/SubscriptionInvites
|
||||
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
||||
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
|
||||
import { UserRequestsController } from '../Controller/UserRequestsController'
|
||||
import { EmailSubscriptionUnsubscribedEventHandler } from '../Domain/Handler/EmailSubscriptionUnsubscribedEventHandler'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -235,6 +236,12 @@ export class ContainerConfigLoader {
|
||||
apiVersion: 'latest',
|
||||
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),
|
||||
@@ -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([
|
||||
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
|
||||
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
|
||||
@@ -576,6 +592,7 @@ export class ContainerConfigLoader {
|
||||
],
|
||||
['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.SharedSubscriptionInvitationCreatedEventHandler)],
|
||||
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
|
||||
['EMAIL_SUBSCRIPTION_UNSUBSCRIBED', container.get(TYPES.EmailSubscriptionUnsubscribedEventHandler)],
|
||||
])
|
||||
|
||||
if (env.get('SQS_QUEUE_URL', true)) {
|
||||
|
||||
@@ -138,6 +138,7 @@ const TYPES = {
|
||||
UserDisabledSessionUserAgentLoggingEventHandler: Symbol.for('UserDisabledSessionUserAgentLoggingEventHandler'),
|
||||
SharedSubscriptionInvitationCreatedEventHandler: Symbol.for('SharedSubscriptionInvitationCreatedEventHandler'),
|
||||
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
|
||||
EmailSubscriptionUnsubscribedEventHandler: Symbol.for('EmailSubscriptionUnsubscribedEventHandler'),
|
||||
// Services
|
||||
DeviceDetector: Symbol.for('DeviceDetector'),
|
||||
SessionService: Symbol.for('SessionService'),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { Request } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
@@ -69,7 +69,7 @@ export class AdminController extends BaseHttpController {
|
||||
const result = await this.doDeleteSetting.execute({
|
||||
uuid,
|
||||
userUuid,
|
||||
settingName: SettingName.MfaSecret,
|
||||
settingName: SettingName.NAMES.MfaSecret,
|
||||
timestamp: updatedAt,
|
||||
softDelete: true,
|
||||
})
|
||||
@@ -115,7 +115,7 @@ export class AdminController extends BaseHttpController {
|
||||
|
||||
const result = await this.doDeleteSetting.execute({
|
||||
userUuid,
|
||||
settingName: SettingName.EmailBackupFrequency,
|
||||
settingName: SettingName.NAMES.EmailBackupFrequency,
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
@@ -21,7 +20,7 @@ export class SubscriptionSettingsController extends BaseHttpController {
|
||||
async getSubscriptionSetting(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.doGetSubscriptionSetting.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
subscriptionSettingName: request.params.subscriptionSettingName as SubscriptionSettingName,
|
||||
subscriptionSettingName: request.params.subscriptionSettingName,
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security'
|
||||
import { ErrorTag, RoleName } from '@standardnotes/common'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
@@ -77,7 +77,7 @@ export class SubscriptionTokensController extends BaseHttpController {
|
||||
const user = authenticateTokenResponse.user as User
|
||||
let extensionKey = undefined
|
||||
const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.ExtensionKey,
|
||||
settingName: SettingName.NAMES.ExtensionKey,
|
||||
userUuid: user.uuid,
|
||||
})
|
||||
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)
|
||||
}
|
||||
9
packages/auth/src/Domain/Email/UserSignedIn.ts
Normal file
9
packages/auth/src/Domain/Email/UserSignedIn.ts
Normal file
@@ -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>`
|
||||
24
packages/auth/src/Domain/Email/user-signed-in.html.ts
Normal file
24
packages/auth/src/Domain/Email/user-signed-in.html.ts
Normal file
@@ -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 */
|
||||
|
||||
import { EmailMessageIdentifier, JSONString, ProtocolVersion, RoleName, Uuid } from '@standardnotes/common'
|
||||
import { JSONString, ProtocolVersion, RoleName, Uuid } from '@standardnotes/common'
|
||||
import {
|
||||
AccountDeletionRequestedEvent,
|
||||
UserEmailChangedEvent,
|
||||
UserRegisteredEvent,
|
||||
UserRolesChangedEvent,
|
||||
OfflineSubscriptionTokenCreatedEvent,
|
||||
EmailBackupRequestedEvent,
|
||||
CloudBackupRequestedEvent,
|
||||
ListedAccountRequestedEvent,
|
||||
UserSignedInEvent,
|
||||
UserDisabledSessionUserAgentLoggingEvent,
|
||||
SharedSubscriptionInvitationCreatedEvent,
|
||||
SharedSubscriptionInvitationCanceledEvent,
|
||||
PredicateVerifiedEvent,
|
||||
DomainEventService,
|
||||
EmailMessageRequestedEvent,
|
||||
WebSocketMessageRequestedEvent,
|
||||
ExitDiscountApplyRequestedEvent,
|
||||
UserContentSizeRecalculationRequestedEvent,
|
||||
MuteEmailsSettingChangedEvent,
|
||||
EmailRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
@@ -32,6 +31,25 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
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 {
|
||||
return {
|
||||
type: 'USER_CONTENT_SIZE_RECALCULATION_REQUESTED',
|
||||
@@ -82,13 +100,15 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
}
|
||||
}
|
||||
|
||||
createEmailMessageRequestedEvent(dto: {
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: EmailMessageIdentifier
|
||||
context: Record<string, unknown>
|
||||
}): EmailMessageRequestedEvent {
|
||||
messageIdentifier: string
|
||||
level: string
|
||||
body: string
|
||||
subject: string
|
||||
}): EmailRequestedEvent {
|
||||
return {
|
||||
type: 'EMAIL_MESSAGE_REQUESTED',
|
||||
type: 'EMAIL_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
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 {
|
||||
return {
|
||||
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: {
|
||||
userUuid: 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 {
|
||||
AccountDeletionRequestedEvent,
|
||||
@@ -6,37 +6,30 @@ import {
|
||||
UserRegisteredEvent,
|
||||
UserRolesChangedEvent,
|
||||
UserEmailChangedEvent,
|
||||
OfflineSubscriptionTokenCreatedEvent,
|
||||
EmailBackupRequestedEvent,
|
||||
ListedAccountRequestedEvent,
|
||||
UserSignedInEvent,
|
||||
UserDisabledSessionUserAgentLoggingEvent,
|
||||
SharedSubscriptionInvitationCreatedEvent,
|
||||
SharedSubscriptionInvitationCanceledEvent,
|
||||
PredicateVerifiedEvent,
|
||||
EmailMessageRequestedEvent,
|
||||
WebSocketMessageRequestedEvent,
|
||||
ExitDiscountApplyRequestedEvent,
|
||||
UserContentSizeRecalculationRequestedEvent,
|
||||
MuteEmailsSettingChangedEvent,
|
||||
EmailRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
|
||||
|
||||
export interface DomainEventFactoryInterface {
|
||||
createUserContentSizeRecalculationRequestedEvent(userUuid: string): UserContentSizeRecalculationRequestedEvent
|
||||
createWebSocketMessageRequestedEvent(dto: { userUuid: Uuid; message: JSONString }): WebSocketMessageRequestedEvent
|
||||
createEmailMessageRequestedEvent(dto: {
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: EmailMessageIdentifier
|
||||
context: Record<string, unknown>
|
||||
}): EmailMessageRequestedEvent
|
||||
createUserSignedInEvent(dto: {
|
||||
userUuid: string
|
||||
userEmail: string
|
||||
device: string
|
||||
browser: string
|
||||
signInAlertEnabled: boolean
|
||||
muteSignInEmailsSettingUuid: Uuid
|
||||
}): UserSignedInEvent
|
||||
messageIdentifier: string
|
||||
level: string
|
||||
body: string
|
||||
subject: string
|
||||
}): EmailRequestedEvent
|
||||
createListedAccountRequestedEvent(userUuid: string, userEmail: string): ListedAccountRequestedEvent
|
||||
createUserRegisteredEvent(dto: {
|
||||
userUuid: string
|
||||
@@ -62,7 +55,6 @@ export interface DomainEventFactoryInterface {
|
||||
}): AccountDeletionRequestedEvent
|
||||
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: RoleName[]): UserRolesChangedEvent
|
||||
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
|
||||
createOfflineSubscriptionTokenCreatedEvent(token: string, email: string): OfflineSubscriptionTokenCreatedEvent
|
||||
createUserDisabledSessionUserAgentLoggingEvent(dto: {
|
||||
userUuid: Uuid
|
||||
email: string
|
||||
@@ -91,4 +83,9 @@ export interface DomainEventFactoryInterface {
|
||||
userEmail: string
|
||||
discountCode: string
|
||||
}): 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 { SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { OfflineFeaturesTokenData } from '@standardnotes/security'
|
||||
import { ContentDecoderInterface } from '@standardnotes/common'
|
||||
import { inject, injectable } from 'inversify'
|
||||
@@ -54,7 +54,7 @@ export class ExtensionKeyGrantedEventHandler implements DomainEventHandlerInterf
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ExtensionKey,
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
unencryptedValue: event.payload.extensionKey,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
sensitive: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DomainEventHandlerInterface, FileRemovedEvent } from '@standardnotes/domain-events'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@@ -38,7 +38,7 @@ export class FileRemovedEventHandler implements DomainEventHandlerInterface {
|
||||
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||
userUuid: user.uuid,
|
||||
userSubscriptionUuid: subscription.uuid,
|
||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
||||
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||
})
|
||||
if (bytesUsedSetting === null) {
|
||||
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({
|
||||
userSubscription: subscription,
|
||||
props: {
|
||||
name: SubscriptionSettingName.FileUploadBytesUsed,
|
||||
name: SettingName.NAMES.FileUploadBytesUsed,
|
||||
unencryptedValue: (+bytesUsed - byteSize).toString(),
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DomainEventHandlerInterface, FileUploadedEvent } from '@standardnotes/domain-events'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@@ -47,7 +47,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface {
|
||||
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||
userUuid: (await subscription.user).uuid,
|
||||
userSubscriptionUuid: subscription.uuid,
|
||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
||||
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||
})
|
||||
if (bytesUsedSetting !== null) {
|
||||
bytesUsed = bytesUsedSetting.value as string
|
||||
@@ -56,7 +56,7 @@ export class FileUploadedEventHandler implements DomainEventHandlerInterface {
|
||||
await this.subscriptionSettingService.createOrReplace({
|
||||
userSubscription: subscription,
|
||||
props: {
|
||||
name: SubscriptionSettingName.FileUploadBytesUsed,
|
||||
name: SettingName.NAMES.FileUploadBytesUsed,
|
||||
unencryptedValue: (+bytesUsed + byteSize).toString(),
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { 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 }
|
||||
|
||||
let authSecrets: ListedAuthorSecretsData = [newSecret]
|
||||
let authSecrets = [newSecret]
|
||||
|
||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.ListedAuthorSecrets,
|
||||
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||
userUuid: user.uuid,
|
||||
})
|
||||
if (listedAuthorSecretsSetting !== null) {
|
||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||
const existingSecrets = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||
existingSecrets.push(newSecret)
|
||||
authSecrets = existingSecrets
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export class ListedAccountCreatedEventHandler implements DomainEventHandlerInter
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ListedAuthorSecrets,
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
unencryptedValue: JSON.stringify(authSecrets),
|
||||
sensitive: false,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { Logger } from 'winston'
|
||||
|
||||
@@ -24,7 +24,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
||||
}
|
||||
|
||||
const listedAuthorSecretsSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.ListedAuthorSecrets,
|
||||
settingName: SettingName.NAMES.ListedAuthorSecrets,
|
||||
userUuid: user.uuid,
|
||||
})
|
||||
if (listedAuthorSecretsSetting === null) {
|
||||
@@ -33,9 +33,9 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
||||
return
|
||||
}
|
||||
|
||||
const existingSecrets: ListedAuthorSecretsData = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||
const existingSecrets = JSON.parse(listedAuthorSecretsSetting.value as string)
|
||||
const filteredSecrets = existingSecrets.filter(
|
||||
(secret) =>
|
||||
(secret: Record<string, unknown>) =>
|
||||
secret.authorId !== event.payload.userId ||
|
||||
(secret.authorId === event.payload.userId && secret.hostUrl !== event.payload.hostUrl),
|
||||
)
|
||||
@@ -43,7 +43,7 @@ export class ListedAccountDeletedEventHandler implements DomainEventHandlerInter
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ListedAuthorSecrets,
|
||||
name: SettingName.NAMES.ListedAuthorSecrets,
|
||||
unencryptedValue: JSON.stringify(filteredSecrets),
|
||||
sensitive: false,
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
@@ -48,7 +48,7 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ExtensionKey,
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
unencryptedValue: event.payload.extensionKey,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
sensitive: true,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { OfflineSettingServiceInterface } from '../Setting/OfflineSettingServiceInterface'
|
||||
import { ContentDecoderInterface } from '@standardnotes/common'
|
||||
import { OfflineSettingName } from '../Setting/OfflineSettingName'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
@@ -95,7 +95,7 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
||||
await this.settingService.createOrReplace({
|
||||
user,
|
||||
props: {
|
||||
name: SettingName.ExtensionKey,
|
||||
name: SettingName.NAMES.ExtensionKey,
|
||||
unencryptedValue: event.payload.extensionKey,
|
||||
serverEncryptionVersion: EncryptionVersion.Default,
|
||||
sensitive: true,
|
||||
|
||||
@@ -12,7 +12,6 @@ import { EphemeralSession } from './EphemeralSession'
|
||||
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
||||
import { RevokedSession } from './RevokedSession'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { LogSessionUserAgentOption } from '@standardnotes/settings'
|
||||
import { Setting } from '../Setting/Setting'
|
||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||
|
||||
@@ -171,7 +170,7 @@ describe('SessionService', () => {
|
||||
user.uuid = '123'
|
||||
|
||||
settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue({
|
||||
value: LogSessionUserAgentOption.Disabled,
|
||||
value: 'disabled',
|
||||
} as jest.Mocked<Setting>)
|
||||
|
||||
const sessionPayload = await createService().createNewSessionForUser({
|
||||
|
||||
@@ -16,7 +16,7 @@ import { EphemeralSession } from './EphemeralSession'
|
||||
import { RevokedSession } from './RevokedSession'
|
||||
import { RevokedSessionRepositoryInterface } from './RevokedSessionRepositoryInterface'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { LogSessionUserAgentOption, SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { SessionBody } from '@standardnotes/responses'
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||
@@ -291,7 +291,7 @@ export class SessionService implements SessionServiceInterface {
|
||||
|
||||
private async isLoggingUserAgentEnabledOnSessions(user: User): Promise<boolean> {
|
||||
const loggingSetting = await this.settingService.findSettingWithDecryptedValue({
|
||||
settingName: SettingName.LogSessionUserAgent,
|
||||
settingName: SettingName.NAMES.LogSessionUserAgent,
|
||||
userUuid: user.uuid,
|
||||
})
|
||||
|
||||
@@ -299,6 +299,6 @@ export class SessionService implements SessionServiceInterface {
|
||||
return true
|
||||
}
|
||||
|
||||
return loggingSetting.value === LogSessionUserAgentOption.Enabled
|
||||
return loggingSetting.value === 'enabled'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
|
||||
export type FindSettingDTO = {
|
||||
userUuid: string
|
||||
settingName: SettingName
|
||||
settingName: string
|
||||
settingUuid?: Uuid
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Uuid } from '@standardnotes/common'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
|
||||
export type FindSubscriptionSettingDTO = {
|
||||
userUuid: Uuid
|
||||
userSubscriptionUuid: Uuid
|
||||
subscriptionSettingName: SubscriptionSettingName
|
||||
subscriptionSettingName: string
|
||||
settingUuid?: Uuid
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@ import {
|
||||
CloudBackupRequestedEvent,
|
||||
DomainEventPublisherInterface,
|
||||
EmailBackupRequestedEvent,
|
||||
MuteEmailsSettingChangedEvent,
|
||||
UserDisabledSessionUserAgentLoggingEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import {
|
||||
EmailBackupFrequency,
|
||||
LogSessionUserAgentOption,
|
||||
OneDriveBackupFrequency,
|
||||
SettingName,
|
||||
} from '@standardnotes/settings'
|
||||
import { MuteMarketingEmailsOption } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import 'reflect-metadata'
|
||||
import { Logger } from 'winston'
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
@@ -57,6 +54,9 @@ describe('SettingInterpreter', () => {
|
||||
domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>)
|
||||
domainEventFactory.createMuteEmailsSettingChangedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<MuteEmailsSettingChangedEvent>)
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
@@ -66,11 +66,11 @@ describe('SettingInterpreter', () => {
|
||||
|
||||
it('should trigger session cleanup if user is disabling session user agent logging', async () => {
|
||||
const setting = {
|
||||
name: SettingName.LogSessionUserAgent,
|
||||
value: LogSessionUserAgentOption.Disabled,
|
||||
name: SettingName.NAMES.LogSessionUserAgent,
|
||||
value: 'disabled',
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, LogSessionUserAgentOption.Disabled)
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'disabled')
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({
|
||||
@@ -81,11 +81,11 @@ describe('SettingInterpreter', () => {
|
||||
|
||||
it('should trigger backup if email backup setting is created - emails not muted', async () => {
|
||||
const setting = {
|
||||
name: SettingName.EmailBackupFrequency,
|
||||
value: EmailBackupFrequency.Daily,
|
||||
name: SettingName.NAMES.EmailBackupFrequency,
|
||||
value: 'daily',
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily)
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'daily')
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
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 () => {
|
||||
const setting = {
|
||||
name: SettingName.EmailBackupFrequency,
|
||||
value: EmailBackupFrequency.Daily,
|
||||
name: SettingName.NAMES.EmailBackupFrequency,
|
||||
value: 'daily',
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
||||
name: SettingName.MuteFailedBackupsEmails,
|
||||
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
uuid: '6-7-8',
|
||||
value: 'muted',
|
||||
} as jest.Mocked<Setting>)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, EmailBackupFrequency.Daily)
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'daily')
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
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 () => {
|
||||
const setting = {
|
||||
name: SettingName.EmailBackupFrequency,
|
||||
value: EmailBackupFrequency.Disabled,
|
||||
name: SettingName.NAMES.EmailBackupFrequency,
|
||||
value: 'disabled',
|
||||
} as jest.Mocked<Setting>
|
||||
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(domainEventFactory.createEmailBackupRequestedEvent).not.toHaveBeenCalled()
|
||||
@@ -123,7 +123,7 @@ describe('SettingInterpreter', () => {
|
||||
|
||||
it('should trigger cloud backup if dropbox backup setting is created', async () => {
|
||||
const setting = {
|
||||
name: SettingName.DropboxBackupToken,
|
||||
name: SettingName.NAMES.DropboxBackupToken,
|
||||
value: 'test-token',
|
||||
} as jest.Mocked<Setting>
|
||||
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 () => {
|
||||
const setting = {
|
||||
name: SettingName.DropboxBackupToken,
|
||||
name: SettingName.NAMES.DropboxBackupToken,
|
||||
value: 'test-token',
|
||||
} as jest.Mocked<Setting>
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue({
|
||||
name: SettingName.MuteFailedCloudBackupsEmails,
|
||||
name: SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
uuid: '6-7-8',
|
||||
value: 'muted',
|
||||
} as jest.Mocked<Setting>)
|
||||
@@ -165,7 +165,7 @@ describe('SettingInterpreter', () => {
|
||||
|
||||
it('should trigger cloud backup if google drive backup setting is created', async () => {
|
||||
const setting = {
|
||||
name: SettingName.GoogleDriveBackupToken,
|
||||
name: SettingName.NAMES.GoogleDriveBackupToken,
|
||||
value: 'test-token',
|
||||
} as jest.Mocked<Setting>
|
||||
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 () => {
|
||||
const setting = {
|
||||
name: SettingName.OneDriveBackupToken,
|
||||
name: SettingName.NAMES.OneDriveBackupToken,
|
||||
value: 'test-token',
|
||||
} as jest.Mocked<Setting>
|
||||
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 () => {
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
||||
name: SettingName.OneDriveBackupToken,
|
||||
name: SettingName.NAMES.OneDriveBackupToken,
|
||||
serverEncryptionVersion: 1,
|
||||
value: 'encrypted-backup-token',
|
||||
sensitive: true,
|
||||
} as jest.Mocked<Setting>)
|
||||
const setting = {
|
||||
name: SettingName.OneDriveBackupFrequency,
|
||||
name: SettingName.NAMES.OneDriveBackupFrequency,
|
||||
serverEncryptionVersion: 0,
|
||||
value: 'daily',
|
||||
sensitive: false,
|
||||
@@ -229,19 +246,19 @@ describe('SettingInterpreter', () => {
|
||||
|
||||
it('should not trigger cloud backup if backup frequency setting is updated as disabled', async () => {
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
|
||||
name: SettingName.OneDriveBackupToken,
|
||||
name: SettingName.NAMES.OneDriveBackupToken,
|
||||
serverEncryptionVersion: 1,
|
||||
value: 'encrypted-backup-token',
|
||||
sensitive: true,
|
||||
} as jest.Mocked<Setting>)
|
||||
const setting = {
|
||||
name: SettingName.OneDriveBackupFrequency,
|
||||
name: SettingName.NAMES.OneDriveBackupFrequency,
|
||||
serverEncryptionVersion: 0,
|
||||
value: OneDriveBackupFrequency.Disabled,
|
||||
value: 'disabled',
|
||||
sensitive: false,
|
||||
} as jest.Mocked<Setting>
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, OneDriveBackupFrequency.Disabled)
|
||||
await createInterpreter().interpretSettingUpdated(setting, user, 'disabled')
|
||||
|
||||
expect(domainEventPublisher.publish).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 () => {
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce(null)
|
||||
const setting = {
|
||||
name: SettingName.OneDriveBackupFrequency,
|
||||
name: SettingName.NAMES.OneDriveBackupFrequency,
|
||||
serverEncryptionVersion: 0,
|
||||
value: 'daily',
|
||||
sensitive: false,
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import {
|
||||
DropboxBackupFrequency,
|
||||
EmailBackupFrequency,
|
||||
GoogleDriveBackupFrequency,
|
||||
LogSessionUserAgentOption,
|
||||
MuteFailedBackupsEmailsOption,
|
||||
MuteFailedCloudBackupsEmailsOption,
|
||||
OneDriveBackupFrequency,
|
||||
SettingName,
|
||||
} from '@standardnotes/settings'
|
||||
import { EmailLevel, SettingName } from '@standardnotes/domain-core'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
@@ -22,22 +13,25 @@ import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
||||
@injectable()
|
||||
export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
private readonly cloudBackupTokenSettings = [
|
||||
SettingName.DropboxBackupToken,
|
||||
SettingName.GoogleDriveBackupToken,
|
||||
SettingName.OneDriveBackupToken,
|
||||
SettingName.NAMES.DropboxBackupToken,
|
||||
SettingName.NAMES.GoogleDriveBackupToken,
|
||||
SettingName.NAMES.OneDriveBackupToken,
|
||||
]
|
||||
|
||||
private readonly cloudBackupFrequencySettings = [
|
||||
SettingName.DropboxBackupFrequency,
|
||||
SettingName.GoogleDriveBackupFrequency,
|
||||
SettingName.OneDriveBackupFrequency,
|
||||
SettingName.NAMES.DropboxBackupFrequency,
|
||||
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||
SettingName.NAMES.OneDriveBackupFrequency,
|
||||
]
|
||||
|
||||
private readonly cloudBackupFrequencyDisabledValues = [
|
||||
DropboxBackupFrequency.Disabled,
|
||||
GoogleDriveBackupFrequency.Disabled,
|
||||
OneDriveBackupFrequency.Disabled,
|
||||
]
|
||||
private readonly cloudBackupFrequencyDisabledValues = ['disabled']
|
||||
|
||||
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<SettingName, string> = new Map([
|
||||
[SettingName.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
|
||||
[SettingName.MuteFailedCloudBackupsEmails, EmailLevel.LEVELS.FailedCloudBackup],
|
||||
[SettingName.MuteMarketingEmails, EmailLevel.LEVELS.Marketing],
|
||||
[SettingName.MuteSignInEmails, EmailLevel.LEVELS.SignIn],
|
||||
])
|
||||
|
||||
constructor(
|
||||
@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> {
|
||||
if (this.isChangingMuteEmailsSetting(updatedSetting)) {
|
||||
await this.triggerEmailSubscriptionChange(user, updatedSetting.name as SettingName, unencryptedValue)
|
||||
}
|
||||
|
||||
if (this.isEnablingEmailBackupSetting(updatedSetting)) {
|
||||
await this.triggerEmailBackup(user.uuid)
|
||||
}
|
||||
@@ -65,11 +63,11 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
let userHasEmailsMuted = false
|
||||
let muteEmailsSettingUuid = ''
|
||||
const muteFailedEmailsBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||
SettingName.MuteFailedBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
userUuid,
|
||||
)
|
||||
if (muteFailedEmailsBackupSetting !== null) {
|
||||
userHasEmailsMuted = muteFailedEmailsBackupSetting.value === MuteFailedBackupsEmailsOption.Muted
|
||||
userHasEmailsMuted = muteFailedEmailsBackupSetting.value === 'muted'
|
||||
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 {
|
||||
return setting.name === SettingName.EmailBackupFrequency && setting.value !== EmailBackupFrequency.Disabled
|
||||
return setting.name === SettingName.NAMES.EmailBackupFrequency && setting.value !== 'disabled'
|
||||
}
|
||||
|
||||
private isEnablingCloudBackupSetting(setting: Setting): boolean {
|
||||
return (
|
||||
(this.cloudBackupFrequencySettings.includes(setting.name as SettingName) ||
|
||||
this.cloudBackupTokenSettings.includes(setting.name as SettingName)) &&
|
||||
!this.cloudBackupFrequencyDisabledValues.includes(
|
||||
setting.value as DropboxBackupFrequency | OneDriveBackupFrequency | GoogleDriveBackupFrequency,
|
||||
)
|
||||
(this.cloudBackupFrequencySettings.includes(setting.name) ||
|
||||
this.cloudBackupTokenSettings.includes(setting.name)) &&
|
||||
!this.cloudBackupFrequencyDisabledValues.includes(setting.value as string)
|
||||
)
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -109,29 +128,26 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
let cloudProvider
|
||||
let tokenSettingName
|
||||
switch (setting.name) {
|
||||
case SettingName.DropboxBackupToken:
|
||||
case SettingName.DropboxBackupFrequency:
|
||||
case SettingName.NAMES.DropboxBackupToken:
|
||||
case SettingName.NAMES.DropboxBackupFrequency:
|
||||
cloudProvider = 'DROPBOX'
|
||||
tokenSettingName = SettingName.DropboxBackupToken
|
||||
tokenSettingName = SettingName.NAMES.DropboxBackupToken
|
||||
break
|
||||
case SettingName.GoogleDriveBackupToken:
|
||||
case SettingName.GoogleDriveBackupFrequency:
|
||||
case SettingName.NAMES.GoogleDriveBackupToken:
|
||||
case SettingName.NAMES.GoogleDriveBackupFrequency:
|
||||
cloudProvider = 'GOOGLE_DRIVE'
|
||||
tokenSettingName = SettingName.GoogleDriveBackupToken
|
||||
tokenSettingName = SettingName.NAMES.GoogleDriveBackupToken
|
||||
break
|
||||
case SettingName.OneDriveBackupToken:
|
||||
case SettingName.OneDriveBackupFrequency:
|
||||
case SettingName.NAMES.OneDriveBackupToken:
|
||||
case SettingName.NAMES.OneDriveBackupFrequency:
|
||||
cloudProvider = 'ONE_DRIVE'
|
||||
tokenSettingName = SettingName.OneDriveBackupToken
|
||||
tokenSettingName = SettingName.NAMES.OneDriveBackupToken
|
||||
break
|
||||
}
|
||||
|
||||
let backupToken = null
|
||||
if (this.cloudBackupFrequencySettings.includes(setting.name as SettingName)) {
|
||||
const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid(
|
||||
tokenSettingName as SettingName,
|
||||
userUuid,
|
||||
)
|
||||
if (this.cloudBackupFrequencySettings.includes(setting.name)) {
|
||||
const tokenSetting = await this.settingRepository.findLastByNameAndUserUuid(tokenSettingName as string, userUuid)
|
||||
if (tokenSetting !== null) {
|
||||
backupToken = await this.settingDecrypter.decryptSettingValue(tokenSetting, userUuid)
|
||||
}
|
||||
@@ -148,11 +164,11 @@ export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
let userHasEmailsMuted = false
|
||||
let muteEmailsSettingUuid = ''
|
||||
const muteFailedCloudBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||
SettingName.MuteFailedCloudBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
userUuid,
|
||||
)
|
||||
if (muteFailedCloudBackupSetting !== null) {
|
||||
userHasEmailsMuted = muteFailedCloudBackupSetting.value === MuteFailedCloudBackupsEmailsOption.Muted
|
||||
userHasEmailsMuted = muteFailedCloudBackupSetting.value === 'muted'
|
||||
muteEmailsSettingUuid = muteFailedCloudBackupSetting.uuid
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { ReadStream } from 'fs'
|
||||
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { DeleteSettingDto } from '../UseCase/DeleteSetting/DeleteSettingDto'
|
||||
import { Setting } from './Setting'
|
||||
|
||||
export interface SettingRepositoryInterface {
|
||||
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>
|
||||
findLastByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
||||
findAllByUserUuid(userUuid: string): Promise<Setting[]>
|
||||
streamAllByNameAndValue(name: SettingName, value: string): Promise<ReadStream>
|
||||
streamAllByNameAndValue(name: string, value: string): Promise<ReadStream>
|
||||
deleteByUserUuid(dto: DeleteSettingDto): Promise<void>
|
||||
save(setting: Setting): Promise<Setting>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { LogSessionUserAgentOption, MuteSignInEmailsOption, SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { User } from '../User/User'
|
||||
@@ -54,9 +54,9 @@ describe('SettingService', () => {
|
||||
settingsAssociationService.getDefaultSettingsAndValuesForNewUser = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SettingName.MuteSignInEmails,
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
{
|
||||
value: MuteSignInEmailsOption.NotMuted,
|
||||
value: 'not_muted',
|
||||
sensitive: 0,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
},
|
||||
@@ -67,11 +67,11 @@ describe('SettingService', () => {
|
||||
settingsAssociationService.getDefaultSettingsAndValuesForNewVaultAccount = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: LogSessionUserAgentOption.Disabled,
|
||||
value: 'disabled',
|
||||
},
|
||||
],
|
||||
]),
|
||||
@@ -173,9 +173,7 @@ describe('SettingService', () => {
|
||||
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||
|
||||
expect(
|
||||
await createService().findSettingWithDecryptedValue({ userUuid: '1-2-3', settingName: 'test' as SettingName }),
|
||||
).toEqual({
|
||||
expect(await createService().findSettingWithDecryptedValue({ userUuid: '1-2-3', settingName: 'test' })).toEqual({
|
||||
serverEncryptionVersion: 1,
|
||||
value: 'decrypted',
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
@@ -74,7 +73,7 @@ export class SettingService implements SettingServiceInterface {
|
||||
|
||||
const existing = await this.findSettingWithDecryptedValue({
|
||||
userUuid: user.uuid,
|
||||
settingName: props.name as SettingName,
|
||||
settingName: props.name,
|
||||
settingUuid: props.uuid,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
|
||||
import { SettingsAssociationService } from './SettingsAssociationService'
|
||||
@@ -11,52 +11,54 @@ describe('SettingsAssociationService', () => {
|
||||
const createService = () => new SettingsAssociationService()
|
||||
|
||||
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', () => {
|
||||
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', () => {
|
||||
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', () => {
|
||||
expect(createService().getEncryptionVersionForSetting(SettingName.EmailBackupFrequency)).toEqual(
|
||||
expect(createService().getEncryptionVersionForSetting(SettingName.NAMES.EmailBackupFrequency)).toEqual(
|
||||
EncryptionVersion.Unencrypted,
|
||||
)
|
||||
})
|
||||
|
||||
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', () => {
|
||||
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', () => {
|
||||
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'])
|
||||
})
|
||||
|
||||
it('should return the default set of settings for a newly registered vault account', () => {
|
||||
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(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', () => {
|
||||
expect(createService().getPermissionAssociatedWithSetting(SettingName.EmailBackupFrequency)).toEqual(
|
||||
expect(createService().getPermissionAssociatedWithSetting(SettingName.NAMES.EmailBackupFrequency)).toEqual(
|
||||
PermissionName.DailyEmailBackup,
|
||||
)
|
||||
})
|
||||
|
||||
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 {
|
||||
LogSessionUserAgentOption,
|
||||
MuteMarketingEmailsOption,
|
||||
MuteSignInEmailsOption,
|
||||
SettingName,
|
||||
} from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { injectable } from 'inversify'
|
||||
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
@@ -15,79 +10,79 @@ import { SettingsAssociationServiceInterface } from './SettingsAssociationServic
|
||||
@injectable()
|
||||
export class SettingsAssociationService implements SettingsAssociationServiceInterface {
|
||||
private readonly UNENCRYPTED_SETTINGS = [
|
||||
SettingName.EmailBackupFrequency,
|
||||
SettingName.MuteFailedBackupsEmails,
|
||||
SettingName.MuteFailedCloudBackupsEmails,
|
||||
SettingName.MuteSignInEmails,
|
||||
SettingName.MuteMarketingEmails,
|
||||
SettingName.DropboxBackupFrequency,
|
||||
SettingName.GoogleDriveBackupFrequency,
|
||||
SettingName.OneDriveBackupFrequency,
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
SettingName.NAMES.DropboxBackupFrequency,
|
||||
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||
SettingName.NAMES.OneDriveBackupFrequency,
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
]
|
||||
|
||||
private readonly UNSENSITIVE_SETTINGS = [
|
||||
SettingName.DropboxBackupFrequency,
|
||||
SettingName.GoogleDriveBackupFrequency,
|
||||
SettingName.OneDriveBackupFrequency,
|
||||
SettingName.EmailBackupFrequency,
|
||||
SettingName.MuteFailedBackupsEmails,
|
||||
SettingName.MuteFailedCloudBackupsEmails,
|
||||
SettingName.MuteSignInEmails,
|
||||
SettingName.MuteMarketingEmails,
|
||||
SettingName.ListedAuthorSecrets,
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.DropboxBackupFrequency,
|
||||
SettingName.NAMES.GoogleDriveBackupFrequency,
|
||||
SettingName.NAMES.OneDriveBackupFrequency,
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
SettingName.NAMES.ListedAuthorSecrets,
|
||||
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>([
|
||||
[SettingName.EmailBackupFrequency, PermissionName.DailyEmailBackup],
|
||||
private readonly permissionsAssociatedWithSettings = new Map<string, PermissionName>([
|
||||
[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,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: MuteSignInEmailsOption.NotMuted,
|
||||
value: 'not_muted',
|
||||
replaceable: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
SettingName.MuteMarketingEmails,
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: MuteMarketingEmailsOption.NotMuted,
|
||||
value: 'not_muted',
|
||||
replaceable: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: LogSessionUserAgentOption.Enabled,
|
||||
value: 'enabled',
|
||||
replaceable: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
private readonly vaultAccountDefaultSettingsOverwrites = new Map<SettingName, SettingDescription>([
|
||||
private readonly vaultAccountDefaultSettingsOverwrites = new Map<string, SettingDescription>([
|
||||
[
|
||||
SettingName.LogSessionUserAgent,
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
{
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: LogSessionUserAgentOption.Disabled,
|
||||
value: 'disabled',
|
||||
replaceable: false,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
isSettingMutableByClient(settingName: SettingName): boolean {
|
||||
isSettingMutableByClient(settingName: string): boolean {
|
||||
if (this.CLIENT_IMMUTABLE_SETTINGS.includes(settingName)) {
|
||||
return false
|
||||
}
|
||||
@@ -95,7 +90,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
return true
|
||||
}
|
||||
|
||||
getSensitivityForSetting(settingName: SettingName): boolean {
|
||||
getSensitivityForSetting(settingName: string): boolean {
|
||||
if (this.UNSENSITIVE_SETTINGS.includes(settingName)) {
|
||||
return false
|
||||
}
|
||||
@@ -103,7 +98,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
return true
|
||||
}
|
||||
|
||||
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion {
|
||||
getEncryptionVersionForSetting(settingName: string): EncryptionVersion {
|
||||
if (this.UNENCRYPTED_SETTINGS.includes(settingName)) {
|
||||
return EncryptionVersion.Unencrypted
|
||||
}
|
||||
@@ -111,7 +106,7 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
return EncryptionVersion.Default
|
||||
}
|
||||
|
||||
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined {
|
||||
getPermissionAssociatedWithSetting(settingName: string): PermissionName | undefined {
|
||||
if (!this.permissionsAssociatedWithSettings.has(settingName)) {
|
||||
return undefined
|
||||
}
|
||||
@@ -119,11 +114,11 @@ export class SettingsAssociationService implements SettingsAssociationServiceInt
|
||||
return this.permissionsAssociatedWithSettings.get(settingName)
|
||||
}
|
||||
|
||||
getDefaultSettingsAndValuesForNewUser(): Map<SettingName, SettingDescription> {
|
||||
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription> {
|
||||
return this.defaultSettings
|
||||
}
|
||||
|
||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<SettingName, SettingDescription> {
|
||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription> {
|
||||
const defaultVaultSettings = new Map(this.defaultSettings)
|
||||
|
||||
for (const vaultAccountDefaultSettingOverwriteKey of this.vaultAccountDefaultSettingsOverwrites.keys()) {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { SettingName, SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
import { SettingDescription } from './SettingDescription'
|
||||
|
||||
export interface SettingsAssociationServiceInterface {
|
||||
getDefaultSettingsAndValuesForNewUser(): Map<SettingName, SettingDescription>
|
||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<SettingName, SettingDescription>
|
||||
getPermissionAssociatedWithSetting(settingName: SettingName): PermissionName | undefined
|
||||
getEncryptionVersionForSetting(settingName: SettingName): EncryptionVersion
|
||||
getSensitivityForSetting(settingName: SettingName): boolean
|
||||
isSettingMutableByClient(settingName: SettingName | SubscriptionSettingName): boolean
|
||||
getDefaultSettingsAndValuesForNewUser(): Map<string, SettingDescription>
|
||||
getDefaultSettingsAndValuesForNewVaultAccount(): Map<string, SettingDescription>
|
||||
getPermissionAssociatedWithSetting(settingName: string): PermissionName | undefined
|
||||
getEncryptionVersionForSetting(settingName: string): EncryptionVersion
|
||||
getSensitivityForSetting(settingName: string): boolean
|
||||
isSettingMutableByClient(settingName: string): boolean
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('SubscriptionSettingService', () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
@@ -102,7 +102,7 @@ describe('SubscriptionSettingService', () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
@@ -127,7 +127,7 @@ describe('SubscriptionSettingService', () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
@@ -152,7 +152,7 @@ describe('SubscriptionSettingService', () => {
|
||||
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{
|
||||
value: '0',
|
||||
sensitive: 0,
|
||||
@@ -266,7 +266,7 @@ describe('SubscriptionSettingService', () => {
|
||||
await createService().findSubscriptionSettingWithDecryptedValue({
|
||||
userSubscriptionUuid: '2-3-4',
|
||||
userUuid: '1-2-3',
|
||||
subscriptionSettingName: 'test' as SubscriptionSettingName,
|
||||
subscriptionSettingName: 'test',
|
||||
}),
|
||||
).toEqual({
|
||||
serverEncryptionVersion: 1,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { SubscriptionName, Uuid } from '@standardnotes/common'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@@ -98,7 +97,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
const existing = await this.findSubscriptionSettingWithDecryptedValue({
|
||||
userUuid: (await userSubscription.user).uuid,
|
||||
userSubscriptionUuid: userSubscription.uuid,
|
||||
subscriptionSettingName: props.name as SubscriptionSettingName,
|
||||
subscriptionSettingName: props.name,
|
||||
settingUuid: props.uuid,
|
||||
})
|
||||
|
||||
@@ -128,7 +127,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
|
||||
}
|
||||
|
||||
private async findPreviousSubscriptionSetting(
|
||||
settingName: SubscriptionSettingName,
|
||||
settingName: string,
|
||||
currentUserSubscriptionUuid: Uuid,
|
||||
userUuid: Uuid,
|
||||
): Promise<SubscriptionSetting | null> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
|
||||
@@ -50,14 +50,11 @@ describe('SubscriptionSettingsAssociationService', () => {
|
||||
|
||||
const flatSettings = [
|
||||
...(
|
||||
settings as Map<
|
||||
SubscriptionSettingName,
|
||||
{ value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }
|
||||
>
|
||||
settings as Map<string, { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }>
|
||||
).keys(),
|
||||
]
|
||||
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,
|
||||
serverEncryptionVersion: 0,
|
||||
value: '107374182400',
|
||||
@@ -78,14 +75,11 @@ describe('SubscriptionSettingsAssociationService', () => {
|
||||
|
||||
const flatSettings = [
|
||||
...(
|
||||
settings as Map<
|
||||
SubscriptionSettingName,
|
||||
{ value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }
|
||||
>
|
||||
settings as Map<string, { value: string; sensitive: boolean; serverEncryptionVersion: EncryptionVersion }>
|
||||
).keys(),
|
||||
]
|
||||
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,
|
||||
serverEncryptionVersion: 0,
|
||||
value: '104857600',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RoleName, SubscriptionName } from '@standardnotes/common'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
@@ -19,15 +19,12 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
||||
@inject(TYPES.RoleRepository) private roleRepository: RoleRepositoryInterface,
|
||||
) {}
|
||||
|
||||
private readonly settingsToSubscriptionNameMap = new Map<
|
||||
SubscriptionName,
|
||||
Map<SubscriptionSettingName, SettingDescription>
|
||||
>([
|
||||
private readonly settingsToSubscriptionNameMap = new Map<SubscriptionName, Map<string, SettingDescription>>([
|
||||
[
|
||||
SubscriptionName.PlusPlan,
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
||||
],
|
||||
]),
|
||||
@@ -36,7 +33,7 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
||||
SubscriptionName.ProPlan,
|
||||
new Map([
|
||||
[
|
||||
SubscriptionSettingName.FileUploadBytesUsed,
|
||||
SettingName.NAMES.FileUploadBytesUsed,
|
||||
{ sensitive: false, serverEncryptionVersion: EncryptionVersion.Unencrypted, value: '0', replaceable: false },
|
||||
],
|
||||
]),
|
||||
@@ -45,14 +42,14 @@ export class SubscriptionSettingsAssociationService implements SubscriptionSetti
|
||||
|
||||
async getDefaultSettingsAndValuesForSubscriptionName(
|
||||
subscriptionName: SubscriptionName,
|
||||
): Promise<Map<SubscriptionSettingName, SettingDescription> | undefined> {
|
||||
): Promise<Map<string, SettingDescription> | undefined> {
|
||||
const defaultSettings = this.settingsToSubscriptionNameMap.get(subscriptionName)
|
||||
|
||||
if (defaultSettings === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
defaultSettings.set(SubscriptionSettingName.FileUploadBytesLimit, {
|
||||
defaultSettings.set(SettingName.NAMES.FileUploadBytesLimit, {
|
||||
sensitive: false,
|
||||
serverEncryptionVersion: EncryptionVersion.Unencrypted,
|
||||
value: (await this.getFileUploadLimit(subscriptionName)).toString(),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
|
||||
import { SettingDescription } from './SettingDescription'
|
||||
|
||||
export interface SubscriptionSettingsAssociationServiceInterface {
|
||||
getDefaultSettingsAndValuesForSubscriptionName(
|
||||
subscriptionName: SubscriptionName,
|
||||
): Promise<Map<SubscriptionSettingName, SettingDescription> | undefined>
|
||||
): Promise<Map<string, SettingDescription> | undefined>
|
||||
getFileUploadLimit(subscriptionName: SubscriptionName): Promise<number>
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { TimerInterface } from '@standardnotes/time'
|
||||
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
||||
|
||||
import { CreateOfflineSubscriptionToken } from './CreateOfflineSubscriptionToken'
|
||||
import { DomainEventPublisherInterface, OfflineSubscriptionTokenCreatedEvent } from '@standardnotes/domain-events'
|
||||
import { DomainEventPublisherInterface, EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscription } from '../../Subscription/OfflineUserSubscription'
|
||||
@@ -47,9 +47,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createOfflineSubscriptionTokenCreatedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<OfflineSubscriptionTokenCreatedEvent>)
|
||||
domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
|
||||
@@ -71,10 +69,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
||||
expiresAt: 1,
|
||||
})
|
||||
|
||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).toHaveBeenCalledWith(
|
||||
'random-string',
|
||||
'test@test.com',
|
||||
)
|
||||
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -91,7 +86,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
||||
})
|
||||
|
||||
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -110,7 +105,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
||||
})
|
||||
|
||||
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -129,7 +124,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
||||
})
|
||||
|
||||
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { EmailLevel } from '@standardnotes/domain-core'
|
||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
@@ -6,6 +7,7 @@ import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
||||
import { getBody, getSubject } from '../../Email/OfflineSubscriptionTokenCreated'
|
||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
@@ -62,7 +64,13 @@ export class CreateOfflineSubscriptionToken implements UseCaseInterface {
|
||||
await this.offlineSubscriptionTokenRepository.save(offlineSubscriptionToken)
|
||||
|
||||
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 {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SubscriptionName } from '@standardnotes/common'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
||||
import { CreateValetTokenPayload, CreateValetTokenResponseData } from '@standardnotes/responses'
|
||||
import { SubscriptionSettingName } from '@standardnotes/settings'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
@@ -56,7 +56,7 @@ export class CreateValetToken implements UseCaseInterface {
|
||||
const uploadBytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||
userUuid: regularSubscriptionUserUuid,
|
||||
userSubscriptionUuid: regularSubscription.uuid,
|
||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesUsed,
|
||||
subscriptionSettingName: SettingName.NAMES.FileUploadBytesUsed,
|
||||
})
|
||||
if (uploadBytesUsedSetting !== null) {
|
||||
uploadBytesUsed = +(uploadBytesUsedSetting.value as string)
|
||||
@@ -70,7 +70,7 @@ export class CreateValetToken implements UseCaseInterface {
|
||||
await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
|
||||
userUuid: regularSubscriptionUserUuid,
|
||||
userSubscriptionUuid: regularSubscription.uuid,
|
||||
subscriptionSettingName: SubscriptionSettingName.FileUploadBytesLimit,
|
||||
subscriptionSettingName: SettingName.NAMES.FileUploadBytesLimit,
|
||||
})
|
||||
if (overwriteWithUserUploadBytesLimitSetting !== null) {
|
||||
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