Compare commits

..

74 Commits

Author SHA1 Message Date
standardci 684ffbadbc chore(release): publish new version
- @standardnotes/analytics@2.12.26
 - @standardnotes/auth-server@1.67.2
 - @standardnotes/domain-core@1.10.0
 - @standardnotes/revisions-server@1.9.27
 - @standardnotes/scheduler-server@1.15.7
 - @standardnotes/syncing-server@1.26.6
 - @standardnotes/workspace-server@1.18.5
2022-12-15 10:38:10 +00:00
Karol Sójko 1c4d4c57de feat(domain-core): add session model 2022-12-15 11:35:49 +01:00
standardci d83111a199 chore(release): publish new version
- @standardnotes/syncing-server@1.26.5
2022-12-15 09:40:56 +00:00
Karol Sójko f10fa839fb fix(syncing-server): revisions processing limit 2022-12-15 10:38:25 +01:00
standardci 1f20395ff3 chore(release): publish new version
- @standardnotes/syncing-server@1.26.4
2022-12-15 07:33:29 +00:00
Karol Sójko bfe6f4255a fix(syncing-server): user uuid field name 2022-12-15 08:31:30 +01:00
standardci b9032f3012 chore(release): publish new version
- @standardnotes/syncing-server@1.26.3
2022-12-15 06:31:07 +00:00
Karol Sójko ce53c459e6 fix(syncing-server): select fields in query for revisions 2022-12-15 07:28:43 +01:00
standardci 6df42fb0d5 chore(release): publish new version
- @standardnotes/syncing-server@1.26.2
2022-12-14 19:08:28 +00:00
Karol Sójko 1e2b496f4f fix(syncing-server): revisions procedure logs 2022-12-14 20:06:07 +01:00
standardci 528c1b0d57 chore(release): publish new version
- @standardnotes/syncing-server@1.26.1
2022-12-14 18:58:11 +00:00
Karol Sójko 22fba8ba80 fix(syncing-server): revisions procedure logs 2022-12-14 19:56:17 +01:00
standardci 6f26261ebe chore(release): publish new version
- @standardnotes/syncing-server@1.26.0
2022-12-14 15:51:14 +00:00
Karol Sójko 4b1fe3ba91 feat(syncing-server): change revisions procedure to pagination instead of streaming 2022-12-14 16:49:21 +01:00
standardci 9f95262bd4 chore(release): publish new version
- @standardnotes/syncing-server@1.25.6
2022-12-14 09:33:24 +00:00
Karol Sójko 2ec28e541e fix(syncing-server): additional stream events handling on revisions procedure 2022-12-14 10:31:06 +01:00
standardci 4764d4b19a chore(release): publish new version
- @standardnotes/syncing-server@1.25.5
2022-12-14 09:00:42 +00:00
Karol Sójko 9b27547dae fix(syncing-server): revisions procedure with env var defined ranges 2022-12-14 09:58:41 +01:00
standardci a96f2c9153 chore(release): publish new version
- @standardnotes/syncing-server@1.25.4
2022-12-13 11:14:35 +00:00
Karol Sójko 225e0aaf88 fix(syncing-server): logs on revisions procedure 2022-12-13 12:12:42 +01:00
standardci f0c85910bc chore(release): publish new version
- @standardnotes/syncing-server@1.25.3
2022-12-13 11:07:23 +00:00
Karol Sójko 124c443528 fix(syncing-server): revisions ownership procedure destructured 2022-12-13 12:05:10 +01:00
standardci 37c7f8d39f chore(release): publish new version
- @standardnotes/syncing-server@1.25.2
2022-12-13 07:19:55 +00:00
Karol Sójko c419f1ce22 fix(syncing-server): change revisions migration to notes 2022-12-13 08:17:55 +01:00
standardci 4949cdfe2f chore(release): publish new version
- @standardnotes/syncing-server@1.25.1
2022-12-13 06:03:06 +00:00
Karol Sójko cd101b96ea fix(syncing-server): revisions procedure properties 2022-12-13 07:01:06 +01:00
standardci 40d0e4631f chore(release): publish new version
- @standardnotes/syncing-server@1.25.0
2022-12-12 19:06:24 +00:00
Karol Sójko a55a995660 feat(syncing-server): fix streaming items for revisions update 2022-12-12 20:03:45 +01:00
standardci 1d576d48ad chore(release): publish new version
- @standardnotes/auth-server@1.67.1
 - @standardnotes/syncing-server@1.24.7
2022-12-12 13:20:47 +00:00
Karol Sójko 4ff8030f87 fix(syncing-server): revisions updating - select fields 2022-12-12 14:18:45 +01:00
Karol Sójko c15e2e2c8f fix: user signed in email template 2022-12-12 14:18:45 +01:00
standardci 41d31a8d75 chore(release): publish new version
- @standardnotes/auth-server@1.67.0
2022-12-12 13:00:40 +00:00
Karol Sójko 10e2a26352 feat(auth): add email subscription unsubscribed event handler 2022-12-12 13:58:35 +01:00
standardci 6e547f77d0 chore(release): publish new version
- @standardnotes/revisions-server@1.9.26
2022-12-12 12:14:52 +00:00
Karol Sójko 530a426601 fix(revisions): responses to match previous response structure 2022-12-12 13:12:46 +01:00
Karol Sójko 642d6bab77 chore: fix triggers for other repos dep 2022-12-12 12:56:28 +01:00
standardci 7980af3d82 chore(release): publish new version
- @standardnotes/analytics@2.12.25
 - @standardnotes/api-gateway@1.40.2
 - @standardnotes/auth-server@1.66.9
 - @standardnotes/domain-events-infra@1.9.56
 - @standardnotes/domain-events@2.104.1
 - @standardnotes/event-store@1.6.53
 - @standardnotes/files-server@1.8.52
 - @standardnotes/revisions-server@1.9.25
 - @standardnotes/scheduler-server@1.15.6
 - @standardnotes/syncing-server@1.24.6
 - @standardnotes/websockets-server@1.4.53
 - @standardnotes/workspace-server@1.18.4
2022-12-12 11:38:37 +00:00
Karol Sójko 2980c42e88 fix(domain-events): add additional domain event services 2022-12-12 12:36:09 +01:00
standardci b03994f9db chore(release): publish new version
- @standardnotes/analytics@2.12.24
2022-12-12 11:31:36 +00:00
Karol Sójko 41906ec2f9 fix(analytics): daily analytics report template 2022-12-12 12:29:16 +01:00
standardci 4d1e7ff2a5 chore(release): publish new version
- @standardnotes/analytics@2.12.23
 - @standardnotes/api-gateway@1.40.1
 - @standardnotes/auth-server@1.66.8
 - @standardnotes/domain-events-infra@1.9.55
 - @standardnotes/domain-events@2.104.0
 - @standardnotes/event-store@1.6.52
 - @standardnotes/files-server@1.8.51
 - @standardnotes/revisions-server@1.9.24
 - @standardnotes/scheduler-server@1.15.5
 - @standardnotes/syncing-server@1.24.5
 - @standardnotes/websockets-server@1.4.52
 - @standardnotes/workspace-server@1.18.3
2022-12-12 11:26:27 +00:00
Karol Sójko 7f18fcfc13 feat(domain-events): add event for email subscription unsubscribed 2022-12-12 12:24:31 +01:00
standardci ff02ce0747 chore(release): publish new version
- @standardnotes/analytics@2.12.22
2022-12-12 10:31:56 +00:00
Karol Sójko a6056600eb fix(analytics): report event publishing 2022-12-12 11:29:41 +01:00
standardci 24c94326d5 chore(release): publish new version
- @standardnotes/analytics@2.12.21
2022-12-12 09:51:01 +00:00
Karol Sójko 48c0cb5e62 fix(analytics): add debug logs for report 2022-12-12 10:49:11 +01:00
standardci 9968efe1b2 chore(release): publish new version
- @standardnotes/syncing-server@1.24.4
2022-12-12 08:49:03 +00:00
Karol Sójko 6368342149 fix(syncing-server): data integrity check on revisions fix 2022-12-12 09:46:35 +01:00
standardci b5f73db210 chore(release): publish new version
- @standardnotes/api-gateway@1.40.0
2022-12-12 04:12:20 +00:00
Karol Sójko 22d6a02d04 feat(api-gateway): add unsubscribe from emails endpoint 2022-12-12 05:10:18 +01:00
standardci 4e0bcfcccf chore(release): publish new version
- @standardnotes/auth-server@1.66.7
2022-12-09 14:30:03 +00:00
Karol Sójko 104313c15d fix(auth): linter issue 2022-12-09 15:27:39 +01:00
standardci 814289af46 chore(release): publish new version
- @standardnotes/analytics@2.12.20
 - @standardnotes/api-gateway@1.39.24
 - @standardnotes/auth-server@1.66.6
 - @standardnotes/domain-events-infra@1.9.54
 - @standardnotes/domain-events@2.103.2
 - @standardnotes/event-store@1.6.51
 - @standardnotes/files-server@1.8.50
 - @standardnotes/revisions-server@1.9.23
 - @standardnotes/scheduler-server@1.15.4
 - @standardnotes/syncing-server@1.24.3
 - @standardnotes/websockets-server@1.4.51
 - @standardnotes/workspace-server@1.18.2
2022-12-09 14:10:16 +00:00
Karol Sójko 3096cd98d5 feat(analytics) replace daily analytics report generated event with email requested 2022-12-09 15:08:17 +01:00
standardci 45dfefbc7a chore(release): publish new version
- @standardnotes/analytics@2.12.19
 - @standardnotes/api-gateway@1.39.23
 - @standardnotes/auth-server@1.66.5
 - @standardnotes/domain-events-infra@1.9.53
 - @standardnotes/domain-events@2.103.1
 - @standardnotes/event-store@1.6.50
 - @standardnotes/files-server@1.8.49
 - @standardnotes/revisions-server@1.9.22
 - @standardnotes/scheduler-server@1.15.3
 - @standardnotes/syncing-server@1.24.2
 - @standardnotes/websockets-server@1.4.50
 - @standardnotes/workspace-server@1.18.1
2022-12-09 13:39:59 +00:00
Karol Sójko 20d92149a8 fix(domain-events): add additional styles option for sending email 2022-12-09 14:37:22 +01:00
standardci 9c01fffca5 chore(release): publish new version
- @standardnotes/analytics@2.12.18
 - @standardnotes/api-gateway@1.39.22
 - @standardnotes/auth-server@1.66.4
 - @standardnotes/domain-events-infra@1.9.52
 - @standardnotes/domain-events@2.103.0
 - @standardnotes/event-store@1.6.49
 - @standardnotes/files-server@1.8.48
 - @standardnotes/revisions-server@1.9.21
 - @standardnotes/scheduler-server@1.15.2
 - @standardnotes/syncing-server@1.24.1
 - @standardnotes/websockets-server@1.4.49
 - @standardnotes/workspace-server@1.18.0
2022-12-09 13:32:57 +00:00
Karol Sójko 61c1cfff4b feat(workspace): replace workspace invite created event with email requested 2022-12-09 14:30:58 +01:00
standardci 7e74261f62 chore(release): publish new version
- @standardnotes/analytics@2.12.17
 - @standardnotes/api-gateway@1.39.21
 - @standardnotes/auth-server@1.66.3
 - @standardnotes/domain-events-infra@1.9.51
 - @standardnotes/domain-events@2.102.0
 - @standardnotes/event-store@1.6.48
 - @standardnotes/files-server@1.8.47
 - @standardnotes/revisions-server@1.9.20
 - @standardnotes/scheduler-server@1.15.1
 - @standardnotes/syncing-server@1.24.0
 - @standardnotes/websockets-server@1.4.48
 - @standardnotes/workspace-server@1.17.47
2022-12-09 13:11:30 +00:00
Karol Sójko 32601f34f1 feat(syncing-server): replace email backup attachment created with email requested 2022-12-09 14:09:30 +01:00
standardci aef69a1a96 chore(release): publish new version
- @standardnotes/analytics@2.12.16
 - @standardnotes/api-gateway@1.39.20
 - @standardnotes/auth-server@1.66.2
 - @standardnotes/domain-events-infra@1.9.50
 - @standardnotes/domain-events@2.101.0
 - @standardnotes/event-store@1.6.47
 - @standardnotes/files-server@1.8.46
 - @standardnotes/revisions-server@1.9.19
 - @standardnotes/scheduler-server@1.15.0
 - @standardnotes/syncing-server@1.23.0
 - @standardnotes/websockets-server@1.4.47
 - @standardnotes/workspace-server@1.17.46
2022-12-09 10:22:39 +00:00
Karol Sójko 130f90bdb6 feat(syncing-server): replace one drive backup failed event with email requested 2022-12-09 11:20:34 +01:00
standardci 851c7de87f chore(release): publish new version
- @standardnotes/syncing-server@1.22.0
2022-12-09 09:35:39 +00:00
Karol Sójko 118156c62d feat(syncing-serfver): remove dropbox backup failed event in favour of email requested 2022-12-09 10:33:40 +01:00
standardci cdad3143c9 chore(release): publish new version
- @standardnotes/analytics@2.12.15
 - @standardnotes/api-gateway@1.39.19
 - @standardnotes/auth-server@1.66.1
 - @standardnotes/domain-events-infra@1.9.49
 - @standardnotes/domain-events@2.100.0
 - @standardnotes/event-store@1.6.46
 - @standardnotes/files-server@1.8.45
 - @standardnotes/revisions-server@1.9.18
 - @standardnotes/scheduler-server@1.14.10
 - @standardnotes/syncing-server@1.21.0
 - @standardnotes/websockets-server@1.4.46
 - @standardnotes/workspace-server@1.17.45
2022-12-09 09:28:27 +00:00
Karol Sójko 00fe32136e feat(syncing-server): remove google drive backup failed event in favour of email requested 2022-12-09 10:26:13 +01:00
standardci 52f56eeb68 chore(release): publish new version
- @standardnotes/auth-server@1.66.0
2022-12-09 09:17:28 +00:00
Karol Sójko b595264e31 feat(email): replace offline subscription token created event in favour of email requested 2022-12-09 10:15:33 +01:00
standardci bf04262170 chore(release): publish new version
- @standardnotes/analytics@2.12.14
 - @standardnotes/api-gateway@1.39.18
 - @standardnotes/auth-server@1.65.0
 - @standardnotes/domain-events-infra@1.9.48
 - @standardnotes/domain-events@2.99.0
 - @standardnotes/event-store@1.6.45
 - @standardnotes/files-server@1.8.44
 - @standardnotes/revisions-server@1.9.17
 - @standardnotes/scheduler-server@1.14.9
 - @standardnotes/syncing-server@1.20.17
 - @standardnotes/websockets-server@1.4.45
 - @standardnotes/workspace-server@1.17.44
2022-12-09 09:03:31 +00:00
Karol Sójko fd589922bb feat(auth): remove offline subscription token created event in favour of email requested 2022-12-09 10:01:38 +01:00
standardci fb7029f5c1 chore(release): publish new version
- @standardnotes/analytics@2.12.13
 - @standardnotes/api-gateway@1.39.17
 - @standardnotes/auth-server@1.64.7
 - @standardnotes/domain-events-infra@1.9.47
 - @standardnotes/domain-events@2.98.4
 - @standardnotes/event-store@1.6.44
 - @standardnotes/files-server@1.8.43
 - @standardnotes/revisions-server@1.9.16
 - @standardnotes/scheduler-server@1.14.8
 - @standardnotes/syncing-server@1.20.16
 - @standardnotes/websockets-server@1.4.44
 - @standardnotes/workspace-server@1.17.43
2022-12-09 07:54:49 +00:00
Karol Sójko cc4b4f9bf8 fix(domain-events): remove unused event 2022-12-09 08:52:51 +01:00
standardci b048d6d7e3 chore(release): publish new version
- @standardnotes/analytics@2.12.12
 - @standardnotes/api-gateway@1.39.16
 - @standardnotes/auth-server@1.64.6
 - @standardnotes/domain-events-infra@1.9.46
 - @standardnotes/domain-events@2.98.3
 - @standardnotes/event-store@1.6.43
 - @standardnotes/files-server@1.8.42
 - @standardnotes/revisions-server@1.9.15
 - @standardnotes/scheduler-server@1.14.7
 - @standardnotes/syncing-server@1.20.15
 - @standardnotes/websockets-server@1.4.43
 - @standardnotes/workspace-server@1.17.42
2022-12-08 14:03:38 +00:00
Karol Sójko cffc1f442f fix(domain-events): remove unused event 2022-12-08 15:01:44 +01:00
130 changed files with 2666 additions and 1115 deletions
@@ -187,7 +187,7 @@ jobs:
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }} tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
- name: Run E2E test suite - name: Run E2E test suite
uses: convictional/trigger-workflow-and-wait@v1.6.3 uses: convictional/trigger-workflow-and-wait@master
with: with:
owner: standardnotes owner: standardnotes
repo: e2e repo: e2e
Generated
+1 -4
View File
@@ -2737,10 +2737,6 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/domain-core/",\ "packageLocation": "./packages/domain-core/",\
"packageDependencies": [\ "packageDependencies": [\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.53.1"],\
["@standardnotes/predicates", "workspace:packages/predicates"],\
["@standardnotes/security", "workspace:packages/security"],\
["@types/jest", "npm:29.1.1"],\ ["@types/jest", "npm:29.1.1"],\
["@types/uuid", "npm:8.3.4"],\ ["@types/uuid", "npm:8.3.4"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\ ["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
@@ -3324,6 +3320,7 @@ const RAW_RUNTIME_STATE =
["@sentry/node", "npm:7.19.0"],\ ["@sentry/node", "npm:7.19.0"],\
["@standardnotes/api", "npm:1.19.0"],\ ["@standardnotes/api", "npm:1.19.0"],\
["@standardnotes/common", "workspace:packages/common"],\ ["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/models", "npm:1.28.0"],\ ["@standardnotes/models", "npm:1.28.0"],\
+66
View File
@@ -3,6 +3,72 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.12.26](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.25...@standardnotes/analytics@2.12.26) (2022-12-15)
**Note:** Version bump only for package @standardnotes/analytics
## [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) ## [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 **Note:** Version bump only for package @standardnotes/analytics
+34 -12
View File
@@ -4,6 +4,7 @@ import 'newrelic'
import { Logger } from 'winston' import { Logger } from 'winston'
import { EmailLevel } from '@standardnotes/domain-core'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity' import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
import { Period } from '../src/Domain/Time/Period' import { Period } from '../src/Domain/Time/Period'
@@ -16,6 +17,8 @@ import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env' import { Env } from '../src/Bootstrap/Env'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue' import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
import { TimerInterface } from '@standardnotes/time'
const requestReport = async ( const requestReport = async (
analyticsStore: AnalyticsStoreInterface, analyticsStore: AnalyticsStoreInterface,
@@ -24,6 +27,8 @@ const requestReport = async (
domainEventPublisher: DomainEventPublisherInterface, domainEventPublisher: DomainEventPublisherInterface,
periodKeyGenerator: PeriodKeyGeneratorInterface, periodKeyGenerator: PeriodKeyGeneratorInterface,
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue, calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
timer: TimerInterface,
adminEmails: string[],
): Promise<void> => { ): Promise<void> => {
await calculateMonthlyRecurringRevenue.execute({}) await calculateMonthlyRecurringRevenue.execute({})
@@ -213,18 +218,29 @@ const requestReport = async (
}) })
} }
const event = domainEventFactory.createDailyAnalyticsReportGeneratedEvent({ for (const adminEmail of adminEmails) {
activityStatistics: yesterdayActivityStatistics, await domainEventPublisher.publish(
activityStatisticsOverTime: analyticsOverTime, domainEventFactory.createEmailRequestedEvent({
statisticsOverTime, messageIdentifier: 'VERSION_ADOPTION_REPORT',
statisticMeasures, subject: getSubject(),
churn: { body: getBody(
periodKeys: monthlyPeriodKeys, {
values: churnRates, activityStatistics: yesterdayActivityStatistics,
}, activityStatisticsOverTime: analyticsOverTime,
}) statisticsOverTime,
statisticMeasures,
await domainEventPublisher.publish(event) churn: {
periodKeys: monthlyPeriodKeys,
values: churnRates,
},
},
timer,
),
level: EmailLevel.LEVELS.System,
userEmail: adminEmail,
}),
)
}
} }
const container = new ContainerConfigLoader() const container = new ContainerConfigLoader()
@@ -241,9 +257,13 @@ void container.load().then((container) => {
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory) const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher) const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator) const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
const timer: TimerInterface = container.get(TYPES.Timer)
const calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue = container.get( const calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue = container.get(
TYPES.CalculateMonthlyRecurringRevenue, TYPES.CalculateMonthlyRecurringRevenue,
) )
const adminEmails = container.get(TYPES.ADMIN_EMAILS) as string[]
logger.info(`Sending report to following admins: ${adminEmails}`)
Promise.resolve( Promise.resolve(
requestReport( requestReport(
@@ -253,6 +273,8 @@ void container.load().then((container) => {
domainEventPublisher, domainEventPublisher,
periodKeyGenerator, periodKeyGenerator,
calculateMonthlyRecurringRevenue, calculateMonthlyRecurringRevenue,
timer,
adminEmails,
), ),
) )
.then(() => { .then(() => {
+3 -3
View File
@@ -5,17 +5,17 @@ COMMAND=$1 && shift 1
case "$COMMAND" in case "$COMMAND" in
'start-worker' ) 'start-worker' )
echo "Starting Worker..." echo "[Docker] Starting Worker..."
yarn workspace @standardnotes/analytics worker yarn workspace @standardnotes/analytics worker
;; ;;
'report' ) 'report' )
echo "Starting Usage Report Generation..." echo "[Docker] Starting Usage Report Generation..."
yarn workspace @standardnotes/analytics report yarn workspace @standardnotes/analytics report
;; ;;
* ) * )
echo "Unknown command" echo "[Docker] Unknown command"
;; ;;
esac esac
+1 -1
View File
@@ -7,5 +7,5 @@ module.exports = {
transform: { transform: {
...tsjPreset.transform, ...tsjPreset.transform,
}, },
coveragePathIgnorePatterns: ['/Infra/'], coveragePathIgnorePatterns: ['/Infra/', '/Domain/Email/'],
} }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/analytics", "name": "@standardnotes/analytics",
"version": "2.12.11", "version": "2.12.26",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
@@ -40,7 +40,7 @@
"@newrelic/winston-enricher": "^4.0.0", "@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.19.0", "@sentry/node": "^7.19.0",
"@standardnotes/common": "workspace:*", "@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:*", "@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*", "@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*", "@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/time": "workspace:*", "@standardnotes/time": "workspace:*",
@@ -130,6 +130,7 @@ export class ContainerConfigLoader {
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true)) container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL')) container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true)) container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
container.bind(TYPES.ADMIN_EMAILS).toConstantValue(env.get('ADMIN_EMAILS').split(','))
// Repositories // Repositories
container container
@@ -11,6 +11,7 @@ const TYPES = {
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'), SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'), REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'), NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
ADMIN_EMAILS: Symbol.for('ADMIN_EMAILS'),
// Repositories // Repositories
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'), AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'), RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
@@ -0,0 +1,11 @@
import { TimerInterface } from '@standardnotes/time'
import { html } from './daily-analytics-report.html'
export function getSubject(): string {
return `Daily analytics report ${new Date().toLocaleDateString('en-US')}`
}
export function getBody(data: unknown, timer: TimerInterface): string {
return html(data, timer)
}
@@ -0,0 +1,966 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { TimerInterface } from '@standardnotes/time'
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
import { Period } from '../Time/Period'
const getChartUrls = (
data: any,
): {
subscriptions: string
users: string
quarterlyPerformance: string
churn: string
mrr: string
mrrMonthly: string
} => {
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
)
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
)
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
)
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
)
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
)
const subscriptionsLinerOverTimeConfig = {
type: 'line',
data: {
labels: subscriptionPurchasingOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
datasets: [
{
label: 'Subscription Purchases',
backgroundColor: 'rgb(25, 255, 140)',
borderColor: 'rgb(25, 255, 140)',
data: subscriptionPurchasingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'Subscription Renewals',
backgroundColor: 'rgb(54, 162, 235)',
borderColor: 'rgb(54, 162, 235)',
data: subscriptionRenewingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'Subscription Refunds',
backgroundColor: 'rgb(255, 221, 51)',
borderColor: 'rgb(255, 221, 51)',
data: subscriptionRefundingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'Subscription Cancels',
backgroundColor: 'rgb(255, 99, 132)',
borderColor: 'rgb(255, 99, 132)',
data: subscriptionCancelledOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'Subscription Reactivations',
backgroundColor: 'rgb(221, 51, 255)',
borderColor: 'rgb(221, 51, 255)',
data: subscriptionReactivatedOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
],
},
}
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
)
const userDeletionOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
)
const usersLinerOverTimeConfig = {
type: 'line',
data: {
labels: userRegistrationOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
datasets: [
{
label: 'User Registrations',
backgroundColor: 'rgb(25, 255, 140)',
borderColor: 'rgb(25, 255, 140)',
data: userRegistrationOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'Account Deletions',
backgroundColor: 'rgb(255, 99, 132)',
borderColor: 'rgb(255, 99, 132)',
data: userDeletionOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
],
},
}
const quarters = [Period.Q1ThisYear, Period.Q2ThisYear, Period.Q3ThisYear, Period.Q4ThisYear]
const quarterlyUserRegistrations = []
const quarterlySubscriptionPurchases = []
const quarterlySubscriptionRenewals = []
for (const quarter of quarters) {
const registrations =
data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.Register && a.period === quarter,
)?.totalCount ?? 0
const purchases =
data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === quarter,
)?.totalCount ?? 0
const renewals =
data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === quarter,
)?.totalCount ?? 0
quarterlyUserRegistrations.push(registrations)
quarterlySubscriptionPurchases.push(purchases)
quarterlySubscriptionRenewals.push(renewals)
}
const quarterlyConfig = {
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [
{
label: 'User Registrations',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 1,
data: quarterlyUserRegistrations,
},
{
label: 'Subscription Purchases',
backgroundColor: 'rgba(54, 162, 235, 0.5)',
borderColor: 'rgb(54, 162, 235)',
borderWidth: 1,
data: quarterlySubscriptionPurchases,
},
{
label: 'Subscription Renewals',
backgroundColor: 'rgb(25, 255, 140, 0.5)',
borderColor: 'rgb(25, 255, 140)',
borderWidth: 1,
data: quarterlySubscriptionRenewals,
},
],
},
options: {
title: {
display: true,
text: 'Quarterly Performance',
},
plugins: {
datalabels: {
anchor: 'center',
align: 'center',
color: '#666',
font: {
weight: 'normal',
},
},
},
},
}
const monthlyChurnRates = data.churn.values.map((value: { rate: number }) => +value.rate.toFixed(2))
const churnConfig = {
type: 'bar',
data: {
labels: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
],
datasets: [
{
label: 'Churn Percent',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
borderColor: 'rgb(255, 99, 132)',
borderWidth: 1,
data: monthlyChurnRates,
},
],
},
options: {
title: {
display: true,
text: 'Monthly Churn Rate',
},
plugins: {
datalabels: {
anchor: 'center',
align: 'center',
color: '#666',
font: {
weight: 'normal',
},
},
},
},
}
const mrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
)
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
)
const annualPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
)
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
)
const proPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
)
const plusPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
)
const mrrOverTimeConfig = {
type: 'line',
data: {
labels: mrrOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
datasets: [
{
label: 'MRR',
backgroundColor: 'rgb(25, 255, 140)',
borderColor: 'rgb(25, 255, 140)',
data: mrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'MRR - Monthly Plans',
backgroundColor: 'rgb(54, 162, 235)',
borderColor: 'rgb(54, 162, 235)',
data: monthlyPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'MRR - Annual Plans',
backgroundColor: 'rgb(255, 221, 51)',
borderColor: 'rgb(255, 221, 51)',
data: annualPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'MRR - Five Year Plans',
backgroundColor: 'rgb(255, 120, 120)',
borderColor: 'rgb(255, 120, 120)',
data: fiveYearPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'MRR - PRO Plans',
backgroundColor: 'rgb(255, 99, 132)',
borderColor: 'rgb(255, 99, 132)',
data: proPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
{
label: 'MRR - PLUS Plans',
backgroundColor: 'rgb(221, 51, 255)',
borderColor: 'rgb(221, 51, 255)',
data: plusPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
fill: false,
pointRadius: 2,
},
],
},
}
const mrrMonthlyOverTime = data.statisticsOverTime
.find((a: { name: string; period: Period }) => a.name === 'mrr' && a.period === Period.ThisYear)
?.counts.map((count: { totalCount: number }) => +count.totalCount.toFixed(2))
const mrrMonthlyConfig = {
type: 'bar',
data: {
labels: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
],
datasets: [
{
label: 'MRR',
backgroundColor: 'rgba(25, 255, 140, 0.5)',
borderColor: 'rgb(25, 255, 140)',
borderWidth: 1,
data: mrrMonthlyOverTime,
},
],
},
options: {
title: {
display: true,
text: 'Monthly MRR',
},
plugins: {
datalabels: {
anchor: 'center',
align: 'center',
color: '#666',
font: {
weight: 'normal',
},
},
},
},
}
return {
subscriptions: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
JSON.stringify(subscriptionsLinerOverTimeConfig),
)}`,
users: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(usersLinerOverTimeConfig))}`,
quarterlyPerformance: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
JSON.stringify(quarterlyConfig),
)}`,
churn: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(churnConfig))}`,
mrr: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrOverTimeConfig))}`,
mrrMonthly: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrMonthlyConfig))}`,
}
}
export const html = (data: any, timer: TimerInterface) => {
const chartUrls = getChartUrls(data)
const successfullPaymentsActivity = data.activityStatistics.find(
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentSuccess && Period.Yesterday,
)
const failedPaymentsActivity = data.activityStatistics.find(
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentFailed && Period.Yesterday,
)
const limitedDiscountPurchasedActivity = data.activityStatistics.find(
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.LimitedDiscountOfferPurchased && Period.Yesterday,
)
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
)
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
)
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
)
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
)
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
)
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
)
const userDeletionOverTime = data.activityStatisticsOverTime.find(
(a: { name: AnalyticsActivity; period: Period }) =>
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
)
const incomeMeasureYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.Income && a.period === Period.Yesterday,
)
const refundMeasureYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.Refunds && a.period === Period.Yesterday,
)
const incomeYesterday = incomeMeasureYesterday?.totalValue ?? 0
const refundsYesterday = refundMeasureYesterday?.totalValue ?? 0
const revenueYesterday = incomeYesterday - refundsYesterday
const subscriptionLengthMeasureYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.Yesterday,
)
const subscriptionLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
Math.floor(subscriptionLengthMeasureYesterday?.average ?? 0),
)
const subscriptionRemainingTimePercentageMeasureYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.Yesterday,
)
const subscriptionRemainingTimePercentageYesterday = Math.floor(
subscriptionRemainingTimePercentageMeasureYesterday?.average ?? 0,
)
const registrationLengthMeasureYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.Yesterday,
)
const registrationLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
Math.floor(registrationLengthMeasureYesterday?.average ?? 0),
)
const registrationToSubscriptionMeasureYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.Yesterday,
)
const registrationToSubscriptionDurationYesterday = timer.convertMicrosecondsToTimeStructure(
Math.floor(registrationToSubscriptionMeasureYesterday?.average ?? 0),
)
const incomeMeasureThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.Income && a.period === Period.ThisMonth,
)
const refundMeasureThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.Refunds && a.period === Period.ThisMonth,
)
const incomeThisMonth = incomeMeasureThisMonth?.totalValue ?? 0
const refundsThisMonth = refundMeasureThisMonth?.totalValue ?? 0
const revenueThisMonth = incomeThisMonth - refundsThisMonth
const subscriptionLengthMeasureThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.ThisMonth,
)
const subscriptionLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
Math.floor(subscriptionLengthMeasureThisMonth?.average ?? 0),
)
const subscriptionRemainingTimePercentageMeasureThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.ThisMonth,
)
const subscriptionRemainingTimePercentageThisMonth = Math.floor(
subscriptionRemainingTimePercentageMeasureThisMonth?.average ?? 0,
)
const registrationLengthMeasureThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.ThisMonth,
)
const registrationLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
Math.floor(registrationLengthMeasureThisMonth?.average ?? 0),
)
const registrationToSubscriptionMeasureThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.ThisMonth,
)
const registrationToSubscriptionDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
Math.floor(registrationToSubscriptionMeasureThisMonth?.average ?? 0),
)
const plusSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
)
const plusSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
)
const plusSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
)
const plusSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
)
const proSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
)
const proSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
)
const proSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
)
const proSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
)
const plusSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
)
const plusSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
)
const plusSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
)
const plusSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
)
const proSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
)
const proSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
)
const proSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
)
const proSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
(a: { name: StatisticsMeasure; period: Period }) =>
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
)
const mrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
)
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
)
const annualPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
)
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
)
const proPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
)
const plusPlansMrrOverTime = data.statisticsOverTime.find(
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
)
const today = new Date()
const thisMonthPeriodKey = `${today.getFullYear().toString()}-${(today.getMonth() + 1).toString()}`
const thisMonthChurn = data.churn.values.find(
(value: { periodKey: string }) => value.periodKey === thisMonthPeriodKey,
)
return ` <div>
<p>Hello,</p>
<p>
<strong>Here are some statistics from yesterday:</strong>
</p>
<ul>
<li>
<b>Payments</b>
<ul>
<li>
Revenue: <b>$${revenueYesterday.toLocaleString('en-US')}</b> (Income: $
${incomeYesterday.toLocaleString('en-US')}, Refunds: $${refundsYesterday.toLocaleString('en-US')})
</li>
<li>
Successfull payments: <b>${successfullPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
</li>
<li>
Failed payments: <b>${failedPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
</li>
</ul>
</li>
<li>
<b>MRR Breakdown</b>
<ul>
<li>
<b>Total:</b> $${mrrOverTime?.counts[mrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
</li>
<li>
<b>By Subscription Type:</b>
<ul>
<li>
<b>PLUS:</b> $
${plusPlansMrrOverTime?.counts[plusPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
</li>
<li>
<b>PRO:</b> $
${proPlansMrrOverTime?.counts[proPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
</li>
</ul>
</li>
<li>
<b>By Billing Frequency:</b>
<ul>
<li>
<b>Monthly:</b> $
${monthlyPlansMrrOverTime?.counts[monthlyPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
'en-US',
)}
</li>
<li>
<b>Annual:</b> $
${annualPlansMrrOverTime?.counts[annualPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
'en-US',
)}
</li>
<li>
<b>5-year:</b> $
${fiveYearPlansMrrOverTime?.counts[fiveYearPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
'en-US',
)}
</li>
</ul>
</li>
</ul>
</li>
<li>
<b>Income Breakdown</b>
<ul>
<li>
<b>Plus Subscription:</b>
<ul>
<li>
<b>${plusSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
<b>$${plusSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${plusSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
<b>$${plusSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
<b>$${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${plusSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
<b>$${plusSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
</li>
</ul>
</li>
<li>
<b>Pro Subscription:</b>
<ul>
<li>
<b>${proSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
<b>$${proSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${proSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
<b>$${proSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${proSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
<b>$${proSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${proSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
<b>$${proSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
</li>
</ul>
</li>
</ul>
</li>
<li>
<b>Users</b>
<ul>
<li>
Number of users registered:${' '}
<b>
${userRegistrationOverTime?.counts[userRegistrationOverTime?.counts.length - 1]?.totalCount.toLocaleString(
'en-US',
)}
</b>
</li>
<li>
Number of users unregistered:${' '}
<b>
${userDeletionOverTime?.counts[userDeletionOverTime?.counts.length - 1]?.totalCount.toLocaleString('en-US')}
</b>${' '}
(average account duration: ${registrationLengthDurationYesterday.days} days${' '}
${registrationLengthDurationYesterday.hours} hours ${registrationLengthDurationYesterday.minutes} minutes)
</li>
</ul>
</li>
<li>
<b>Subscriptions</b>
<ul>
<li>
Number of subscriptions purchased:${' '}
<b>
${subscriptionPurchasingOverTime?.counts[
subscriptionPurchasingOverTime?.counts.length - 1
]?.totalCount.toLocaleString('en-US')}
</b>${' '}
(includes <b>${limitedDiscountPurchasedActivity?.totalCount.toLocaleString('en-US')}</b> limited time
offer purchases)
</li>
<li>
Number of subscriptions renewed:${' '}
<b>
${subscriptionRenewingOverTime?.counts[
subscriptionRenewingOverTime?.counts.length - 1
]?.totalCount.toLocaleString('en-US')}
</b>
</li>
<li>
Number of subscriptions refunded:${' '}
<b>
${subscriptionRefundingOverTime?.counts[
subscriptionRefundingOverTime?.counts.length - 1
]?.totalCount.toLocaleString('en-US')}
</b>
</li>
<li>
Number of subscriptions cancelled:${' '}
<b>
${subscriptionCancelledOverTime?.counts[
subscriptionCancelledOverTime?.counts.length - 1
]?.totalCount.toLocaleString('en-US')}
</b>${' '}
(average subscription duration: ${subscriptionLengthDurationYesterday.days} days${' '}
${subscriptionLengthDurationYesterday.hours} hours ${subscriptionLengthDurationYesterday.minutes} minutes,
average remaining subscription percentage: ${subscriptionRemainingTimePercentageYesterday}%)
</li>
<li>
Number of subscriptions reactivated:${' '}
<b>
${subscriptionReactivatedOverTime?.counts[
subscriptionReactivatedOverTime?.counts.length - 1
]?.totalCount.toLocaleString('en-US')}
</b>
</li>
<li>
Average time from registration to subscription purchase:${' '}
<b>
${registrationToSubscriptionDurationYesterday.days} days${' '}
${registrationToSubscriptionDurationYesterday.hours} hours${' '}
${registrationToSubscriptionDurationYesterday.minutes} minutes
</b>
</li>
</ul>
</li>
</ul>
<p>
<strong>Here are some statistics from last 30 days:</strong>
</p>
<ul>
<li>
<b>Payments (This Month)</b>
<ul>
<li>
Revenue: <b>$${revenueThisMonth.toLocaleString('en-US')}</b>
</li>
<li>
Income: <b>$${incomeThisMonth.toLocaleString('en-US')}</b>
</li>
<li>
Refunds: <b>$${refundsThisMonth.toLocaleString('en-US')}</b>
</li>
</ul>
</li>
<li>
<b>Income Breakdown (This Month)</b>
<ul>
<li>
<b>Plus Subscription:</b>
<ul>
<li>
<b>${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
<b>$${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${plusSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
<b>$${plusSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
<b>$${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
<b>$${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
</li>
</ul>
</li>
<li>
<b>Pro Subscription:</b>
<ul>
<li>
<b>${proSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
<b>$${proSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${proSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
<b>$${proSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
<b>$${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
</li>
<li>
<b>${proSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
<b>$${proSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
</li>
</ul>
</li>
</ul>
</li>
<li>
<b>Users</b>
<ul>
<li>
Number of users registered: <b>${userRegistrationOverTime?.totalCount.toLocaleString('en-US')}</b>
</li>
<li>
Number of users unregistered: <b>${userDeletionOverTime?.totalCount.toLocaleString('en-US')}</b>
</li>
<li>
Average account duration this month:${' '}
<b>
${registrationLengthDurationThisMonth.days} days ${registrationLengthDurationThisMonth.hours} hours${' '}
${registrationLengthDurationThisMonth.minutes} minutes
</b>
</li>
</ul>
</li>
<li>
<b>Subscriptions</b>
<ul>
<li>
Number of subscriptions purchased:${' '}
<b>${subscriptionPurchasingOverTime?.totalCount.toLocaleString('en-US')}</b>
</li>
<li>
Number of subscriptions renewed:${' '}
<b>${subscriptionRenewingOverTime?.totalCount.toLocaleString('en-US')}</b>
</li>
<li>
Number of subscriptions refunded:${' '}
<b>${subscriptionRefundingOverTime?.totalCount.toLocaleString('en-US')}</b>
</li>
<li>
Number of subscriptions cancelled:${' '}
<b>${subscriptionCancelledOverTime?.totalCount.toLocaleString('en-US')}</b>
</li>
<li>
Number of subscriptions reactivated:${' '}
<b>${subscriptionReactivatedOverTime?.totalCount.toLocaleString('en-US')}</b>
</li>
<li>
Average subscription duration this month:${' '}
<b>
${subscriptionLengthDurationThisMonth.days} days ${subscriptionLengthDurationThisMonth.hours} hours${' '}
${subscriptionLengthDurationThisMonth.minutes} minutes
</b>
</li>
<li>
Average subscription remaining percentage this month:${' '}
<b>${subscriptionRemainingTimePercentageThisMonth}%</b>
</li>
<li>
Average time from registration to subscription purchase this month:${' '}
<b>
${registrationToSubscriptionDurationThisMonth.days} days${' '}
${registrationToSubscriptionDurationThisMonth.hours} hours${' '}
${registrationToSubscriptionDurationThisMonth.minutes} minutes
</b>
</li>
</ul>
</li>
</ul>
<p>
<strong>Here is the MRR chart over 30 days:</strong>
</p>
<img src=${chartUrls.mrr}></img>
<p>
<strong>Here is the MRR Monthly chart this year:</strong>
</p>
<img src=${chartUrls.mrrMonthly}></img>
<p>
<strong>Here is the subscription chart over 30 days:</strong>
</p>
<img src=${chartUrls.subscriptions}></img>
<p>
<strong>Here is the users chart over 30 days:</strong>
</p>
<img src=${chartUrls.users}></img>
<p>
<strong>Here is the monthly churn rate percentage:</strong>
</p>
<p>✅ GREAT! Up to 7% 🔶 OKAY: 8-10% 🩸 BAD: 11 -15 % 🚨 TERRIBLE! 16-20%</p>
<p>Churn is calculated by the following formula:</p>
<p>
( Existing Customers Churn [${thisMonthChurn?.existingCustomersChurn}] + New Customers Churn [
${thisMonthChurn?.newCustomersChurn}] ) * 100 / Average Customers Count This Month [
${thisMonthChurn?.averageCustomersCount}]
</p>
<img src=${chartUrls.churn}></img>
<p>
<strong>Here is quarterly performance chart:</strong>
</p>
<img src=${chartUrls.quarterlyPerformance}></img>
<p>Thanks,SN</p>
</div>`
}
@@ -1,6 +1,6 @@
/* istanbul ignore file */ /* istanbul ignore file */
import { DomainEventService, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events' import { DomainEventService, EmailRequestedEvent } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify' import { inject, injectable } from 'inversify'
import TYPES from '../../Bootstrap/Types' import TYPES from '../../Bootstrap/Types'
@@ -9,55 +9,20 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
@injectable() @injectable()
export class DomainEventFactory implements DomainEventFactoryInterface { export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {} constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createEmailRequestedEvent(dto: {
createDailyAnalyticsReportGeneratedEvent(dto: { userEmail: string
activityStatistics: Array<{ messageIdentifier: string
name: string level: string
retention: number body: string
totalCount: number subject: string
}> }): EmailRequestedEvent {
statisticMeasures: Array<{
name: string
totalValue: number
average: number
increments: number
period: number
}>
activityStatisticsOverTime: Array<{
name: string
period: number
counts: Array<{
periodKey: string
totalCount: number
}>
totalCount: number
}>
statisticsOverTime: Array<{
name: string
period: number
counts: Array<{
periodKey: string
totalCount: number
}>
}>
churn: {
periodKeys: Array<string>
values: Array<{
rate: number
periodKey: string
averageCustomersCount: number
existingCustomersChurn: number
newCustomersChurn: number
}>
}
}): DailyAnalyticsReportGeneratedEvent {
return { return {
type: 'DAILY_ANALYTICS_REPORT_GENERATED', type: 'EMAIL_REQUESTED',
createdAt: this.timer.getUTCDate(), createdAt: this.timer.getUTCDate(),
meta: { meta: {
correlation: { correlation: {
userIdentifier: '', userIdentifier: dto.userEmail,
userIdentifierType: 'uuid', userIdentifierType: 'email',
}, },
origin: DomainEventService.Analytics, origin: DomainEventService.Analytics,
}, },
@@ -1,45 +1,11 @@
import { DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events' import { EmailRequestedEvent } from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface { export interface DomainEventFactoryInterface {
createDailyAnalyticsReportGeneratedEvent(dto: { createEmailRequestedEvent(dto: {
activityStatistics: Array<{ userEmail: string
name: string messageIdentifier: string
retention: number level: string
totalCount: number body: string
}> subject: string
statisticMeasures: Array<{ }): EmailRequestedEvent
name: string
totalValue: number
average: number
increments: number
period: number
}>
activityStatisticsOverTime: Array<{
name: string
period: number
counts: Array<{
periodKey: string
totalCount: number
}>
totalCount: number
}>
statisticsOverTime: Array<{
name: string
period: number
counts: Array<{
periodKey: string
totalCount: number
}>
}>
churn: {
periodKeys: Array<string>
values: Array<{
rate: number
periodKey: string
averageCustomersCount: number
existingCustomersChurn: number
newCustomersChurn: number
}>
}
}): DailyAnalyticsReportGeneratedEvent
} }
+1
View File
@@ -11,6 +11,7 @@ WEB_SOCKET_SERVER_URL=http://websockets:3000
PAYMENTS_SERVER_URL=http://payments:3000 PAYMENTS_SERVER_URL=http://payments:3000
FILES_SERVER_URL=http://files:3000 FILES_SERVER_URL=http://files:3000
REVISIONS_SERVER_URL=http://revisions:3000 REVISIONS_SERVER_URL=http://revisions:3000
EMAIL_SERVER_URL=http://email:3000
HTTP_CALL_TIMEOUT=60000 HTTP_CALL_TIMEOUT=60000
+50
View File
@@ -3,6 +3,56 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.40.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.40.1...@standardnotes/api-gateway@1.40.2) (2022-12-12)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.40.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.40.0...@standardnotes/api-gateway@1.40.1) (2022-12-12)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.40.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.24...@standardnotes/api-gateway@1.40.0) (2022-12-12)
### Features
* **api-gateway:** add unsubscribe from emails endpoint ([22d6a02](https://github.com/standardnotes/api-gateway/commit/22d6a02d049ba3bde890c7def91e19f013ba3e22))
## [1.39.24](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.23...@standardnotes/api-gateway@1.39.24) (2022-12-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.23](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.22...@standardnotes/api-gateway@1.39.23) (2022-12-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.22](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.21...@standardnotes/api-gateway@1.39.22) (2022-12-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.21](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.20...@standardnotes/api-gateway@1.39.21) (2022-12-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.20](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.19...@standardnotes/api-gateway@1.39.20) (2022-12-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.19](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.18...@standardnotes/api-gateway@1.39.19) (2022-12-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.18](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.17...@standardnotes/api-gateway@1.39.18) (2022-12-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.17](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.16...@standardnotes/api-gateway@1.39.17) (2022-12-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.16](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.15...@standardnotes/api-gateway@1.39.16) (2022-12-08)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.15](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.14...@standardnotes/api-gateway@1.39.15) (2022-12-08) ## [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 **Note:** Version bump only for package @standardnotes/api-gateway
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/api-gateway", "name": "@standardnotes/api-gateway",
"version": "1.39.15", "version": "1.40.2",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
@@ -55,6 +55,7 @@ export class ContainerConfigLoader {
container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL')) container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL'))
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL')) container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
container.bind(TYPES.REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL', true)) container.bind(TYPES.REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL', true))
container.bind(TYPES.EMAIL_SERVER_URL).toConstantValue(env.get('EMAIL_SERVER_URL', true))
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true)) container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true)) container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET')) container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
@@ -8,6 +8,7 @@ const TYPES = {
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'), PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'), FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
REVISIONS_SERVER_URL: Symbol.for('REVISIONS_SERVER_URL'), REVISIONS_SERVER_URL: Symbol.for('REVISIONS_SERVER_URL'),
EMAIL_SERVER_URL: Symbol.for('EMAIL_SERVER_URL'),
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'), WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'),
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'), WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'), AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
@@ -29,4 +29,14 @@ export class ActionsController extends BaseHttpController {
async methods(request: Request, response: Response): Promise<void> { async methods(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/methods', request.body) await this.httpService.callAuthServer(request, response, 'auth/methods', request.body)
} }
@httpGet('/unsubscribe/:token')
async emailUnsubscribe(request: Request, response: Response): Promise<void> {
await this.httpService.callEmailServer(
request,
response,
`subscriptions/actions/unsubscribe/${request.params.token}`,
request.body,
)
}
} }
@@ -19,6 +19,7 @@ export class HttpService implements HttpServiceInterface {
@inject(TYPES.WORKSPACE_SERVER_URL) private workspaceServerUrl: string, @inject(TYPES.WORKSPACE_SERVER_URL) private workspaceServerUrl: string,
@inject(TYPES.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string, @inject(TYPES.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
@inject(TYPES.REVISIONS_SERVER_URL) private revisionsServerUrl: string, @inject(TYPES.REVISIONS_SERVER_URL) private revisionsServerUrl: string,
@inject(TYPES.EMAIL_SERVER_URL) private emailServerUrl: string,
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number, @inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface, @inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.Logger) private logger: Logger, @inject(TYPES.Logger) private logger: Logger,
@@ -65,6 +66,21 @@ export class HttpService implements HttpServiceInterface {
await this.callServer(this.authServerUrl, request, response, endpoint, payload) await this.callServer(this.authServerUrl, request, response, endpoint, payload)
} }
async callEmailServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.emailServerUrl) {
response.status(400).send({ message: 'Email Server not configured' })
return
}
await this.callServer(this.emailServerUrl, request, response, endpoint, payload)
}
async callWorkspaceServer( async callWorkspaceServer(
request: Request, request: Request,
response: Response, response: Response,
@@ -1,6 +1,12 @@
import { Request, Response } from 'express' import { Request, Response } from 'express'
export interface HttpServiceInterface { export interface HttpServiceInterface {
callEmailServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
callAuthServer( callAuthServer(
request: Request, request: Request,
response: Response, response: Response,
+74
View File
@@ -3,6 +3,80 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.67.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.67.1...@standardnotes/auth-server@1.67.2) (2022-12-15)
**Note:** Version bump only for package @standardnotes/auth-server
## [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) ## [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 **Note:** Version bump only for package @standardnotes/auth-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/auth-server", "name": "@standardnotes/auth-server",
"version": "1.64.5", "version": "1.67.2",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
+11
View File
@@ -193,6 +193,7 @@ import { SubscriptionInvitesController } from '../Controller/SubscriptionInvites
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken' import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest' import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
import { UserRequestsController } from '../Controller/UserRequestsController' import { UserRequestsController } from '../Controller/UserRequestsController'
import { EmailSubscriptionUnsubscribedEventHandler } from '../Domain/Handler/EmailSubscriptionUnsubscribedEventHandler'
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher') const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -560,6 +561,15 @@ export class ContainerConfigLoader {
) )
} }
container
.bind<EmailSubscriptionUnsubscribedEventHandler>(TYPES.EmailSubscriptionUnsubscribedEventHandler)
.toConstantValue(
new EmailSubscriptionUnsubscribedEventHandler(
container.get(TYPES.UserRepository),
container.get(TYPES.SettingService),
),
)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([ const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)], ['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)], ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
@@ -582,6 +592,7 @@ export class ContainerConfigLoader {
], ],
['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.SharedSubscriptionInvitationCreatedEventHandler)], ['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.SharedSubscriptionInvitationCreatedEventHandler)],
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)], ['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
['EMAIL_SUBSCRIPTION_UNSUBSCRIBED', container.get(TYPES.EmailSubscriptionUnsubscribedEventHandler)],
]) ])
if (env.get('SQS_QUEUE_URL', true)) { if (env.get('SQS_QUEUE_URL', true)) {
+1
View File
@@ -138,6 +138,7 @@ const TYPES = {
UserDisabledSessionUserAgentLoggingEventHandler: Symbol.for('UserDisabledSessionUserAgentLoggingEventHandler'), UserDisabledSessionUserAgentLoggingEventHandler: Symbol.for('UserDisabledSessionUserAgentLoggingEventHandler'),
SharedSubscriptionInvitationCreatedEventHandler: Symbol.for('SharedSubscriptionInvitationCreatedEventHandler'), SharedSubscriptionInvitationCreatedEventHandler: Symbol.for('SharedSubscriptionInvitationCreatedEventHandler'),
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'), PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
EmailSubscriptionUnsubscribedEventHandler: Symbol.for('EmailSubscriptionUnsubscribedEventHandler'),
// Services // Services
DeviceDetector: Symbol.for('DeviceDetector'), DeviceDetector: Symbol.for('DeviceDetector'),
SessionService: Symbol.for('SessionService'), SessionService: Symbol.for('SessionService'),
@@ -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)
}
@@ -5,11 +5,5 @@ export function getSubject(email: string): string {
} }
export function getBody(email: string, device: string, browser: string, date: Date): string { export function getBody(email: string, device: string, browser: string, date: Date): string {
const body = html return html(email, device, browser, date.toLocaleString())
return body
.replace('%%EMAIL%%', email)
.replace('%%DEVICE%%', device)
.replace('%%BROWSER%%', browser)
.replace('%%TIME_AND_DATE%%', 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>`
@@ -1,15 +1,15 @@
export const html = ` export const html = (email: string, device: string, browser: string, timeAndDate: string) => `
<div> <div>
<p>Hello,</p> <p>Hello,</p>
<p>We've detected a new sign-in to your account %%EMAIL%%.</p> <p>We've detected a new sign-in to your account ${email}</p>
<p> <p>
<b>Device type</b>: %%DEVICE%% <b>Device type</b>: ${device}
</p> </p>
<p> <p>
<b>Browser type</b>: %%BROWSER%% <b>Browser type</b>: ${browser}
</p> </p>
<p> <p>
<strong>Time and date</strong>: <span>%%TIME_AND_DATE%%</span> <strong>Time and date</strong>: <span>${timeAndDate}</span>
</p> </p>
<p> <p>
If this was you, please disregard this email. If it wasn't you, we recommend signing into your account and If this was you, please disregard this email. If it wasn't you, we recommend signing into your account and
@@ -20,6 +20,5 @@ export const html = `
<br /> <br />
SN SN
</p> </p>
<a href="https://app.standardnotes.com/?settings=account">Mute these emails</a>
</div> </div>
` `
@@ -6,7 +6,6 @@ import {
UserEmailChangedEvent, UserEmailChangedEvent,
UserRegisteredEvent, UserRegisteredEvent,
UserRolesChangedEvent, UserRolesChangedEvent,
OfflineSubscriptionTokenCreatedEvent,
EmailBackupRequestedEvent, EmailBackupRequestedEvent,
CloudBackupRequestedEvent, CloudBackupRequestedEvent,
ListedAccountRequestedEvent, ListedAccountRequestedEvent,
@@ -290,24 +289,6 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
} }
} }
createOfflineSubscriptionTokenCreatedEvent(token: string, email: string): OfflineSubscriptionTokenCreatedEvent {
return {
type: 'OFFLINE_SUBSCRIPTION_TOKEN_CREATED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: email,
userIdentifierType: 'email',
},
origin: DomainEventService.Auth,
},
payload: {
token,
email,
},
}
}
createUserRegisteredEvent(dto: { createUserRegisteredEvent(dto: {
userUuid: string userUuid: string
email: string email: string
@@ -6,7 +6,6 @@ import {
UserRegisteredEvent, UserRegisteredEvent,
UserRolesChangedEvent, UserRolesChangedEvent,
UserEmailChangedEvent, UserEmailChangedEvent,
OfflineSubscriptionTokenCreatedEvent,
EmailBackupRequestedEvent, EmailBackupRequestedEvent,
ListedAccountRequestedEvent, ListedAccountRequestedEvent,
UserDisabledSessionUserAgentLoggingEvent, UserDisabledSessionUserAgentLoggingEvent,
@@ -56,7 +55,6 @@ export interface DomainEventFactoryInterface {
}): AccountDeletionRequestedEvent }): AccountDeletionRequestedEvent
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: RoleName[]): UserRolesChangedEvent createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: RoleName[]): UserRolesChangedEvent
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
createOfflineSubscriptionTokenCreatedEvent(token: string, email: string): OfflineSubscriptionTokenCreatedEvent
createUserDisabledSessionUserAgentLoggingEvent(dto: { createUserDisabledSessionUserAgentLoggingEvent(dto: {
userUuid: Uuid userUuid: Uuid
email: string email: string
@@ -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}`)
}
}
}
@@ -5,7 +5,7 @@ import { TimerInterface } from '@standardnotes/time'
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface' import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
import { CreateOfflineSubscriptionToken } from './CreateOfflineSubscriptionToken' import { CreateOfflineSubscriptionToken } from './CreateOfflineSubscriptionToken'
import { DomainEventPublisherInterface, OfflineSubscriptionTokenCreatedEvent } from '@standardnotes/domain-events' import { DomainEventPublisherInterface, EmailRequestedEvent } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface' import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { OfflineUserSubscription } from '../../Subscription/OfflineUserSubscription' import { OfflineUserSubscription } from '../../Subscription/OfflineUserSubscription'
@@ -47,9 +47,7 @@ describe('CreateOfflineSubscriptionToken', () => {
domainEventPublisher.publish = jest.fn() domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface> domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createOfflineSubscriptionTokenCreatedEvent = jest domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
.fn()
.mockReturnValue({} as jest.Mocked<OfflineSubscriptionTokenCreatedEvent>)
timer = {} as jest.Mocked<TimerInterface> timer = {} as jest.Mocked<TimerInterface>
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1) timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
@@ -71,10 +69,7 @@ describe('CreateOfflineSubscriptionToken', () => {
expiresAt: 1, expiresAt: 1,
}) })
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).toHaveBeenCalledWith( expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
'random-string',
'test@test.com',
)
expect(domainEventPublisher.publish).toHaveBeenCalled() expect(domainEventPublisher.publish).toHaveBeenCalled()
}) })
@@ -91,7 +86,7 @@ describe('CreateOfflineSubscriptionToken', () => {
}) })
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled() expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled() expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled()
}) })
@@ -110,7 +105,7 @@ describe('CreateOfflineSubscriptionToken', () => {
}) })
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled() expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled() expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled()
}) })
@@ -129,7 +124,7 @@ describe('CreateOfflineSubscriptionToken', () => {
}) })
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled() expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled() expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled()
}) })
}) })
@@ -1,4 +1,5 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { EmailLevel } from '@standardnotes/domain-core'
import { CryptoNode } from '@standardnotes/sncrypto-node' import { CryptoNode } from '@standardnotes/sncrypto-node'
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify' import { inject, injectable } from 'inversify'
@@ -6,6 +7,7 @@ import { Logger } from 'winston'
import TYPES from '../../../Bootstrap/Types' import TYPES from '../../../Bootstrap/Types'
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface' import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
import { getBody, getSubject } from '../../Email/OfflineSubscriptionTokenCreated'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface' import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { UseCaseInterface } from '../UseCaseInterface' import { UseCaseInterface } from '../UseCaseInterface'
@@ -62,7 +64,13 @@ export class CreateOfflineSubscriptionToken implements UseCaseInterface {
await this.offlineSubscriptionTokenRepository.save(offlineSubscriptionToken) await this.offlineSubscriptionTokenRepository.save(offlineSubscriptionToken)
await this.domainEventPublisher.publish( await this.domainEventPublisher.publish(
this.domainEventFactory.createOfflineSubscriptionTokenCreatedEvent(token, dto.userEmail), this.domainEventFactory.createEmailRequestedEvent({
body: getBody(dto.userEmail, `https://standardnotes.com/dashboard/offline?subscription_token=${token}`),
level: EmailLevel.LEVELS.System,
subject: getSubject(),
messageIdentifier: 'OFFLINE_SUBSCRIPTION_ACCESS',
userEmail: dto.userEmail,
}),
) )
return { return {
@@ -1,6 +1,10 @@
import 'reflect-metadata' import 'reflect-metadata'
import { DomainEventPublisherInterface, SharedSubscriptionInvitationCreatedEvent } from '@standardnotes/domain-events' import {
DomainEventPublisherInterface,
SharedSubscriptionInvitationCreatedEvent,
EmailRequestedEvent,
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { SharedSubscriptionInvitationRepositoryInterface } from '../../SharedSubscription/SharedSubscriptionInvitationRepositoryInterface' import { SharedSubscriptionInvitationRepositoryInterface } from '../../SharedSubscription/SharedSubscriptionInvitationRepositoryInterface'
@@ -51,6 +55,7 @@ describe('InviteToSharedSubscription', () => {
domainEventFactory.createSharedSubscriptionInvitationCreatedEvent = jest domainEventFactory.createSharedSubscriptionInvitationCreatedEvent = jest
.fn() .fn()
.mockReturnValue({} as jest.Mocked<SharedSubscriptionInvitationCreatedEvent>) .mockReturnValue({} as jest.Mocked<SharedSubscriptionInvitationCreatedEvent>)
domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
}) })
it('should not create an inivitation for sharing the subscription if inviter has no subscription', async () => { it('should not create an inivitation for sharing the subscription if inviter has no subscription', async () => {
@@ -1,9 +1,11 @@
import { RoleName } from '@standardnotes/common' import { RoleName } from '@standardnotes/common'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { EmailLevel } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify' import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types' import TYPES from '../../../Bootstrap/Types'
import { getBody, getSubject } from '../../Email/SharedSubscriptionInvitationCreated'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { InvitationStatus } from '../../SharedSubscription/InvitationStatus' import { InvitationStatus } from '../../SharedSubscription/InvitationStatus'
import { InviteeIdentifierType } from '../../SharedSubscription/InviteeIdentifierType' import { InviteeIdentifierType } from '../../SharedSubscription/InviteeIdentifierType'
@@ -89,6 +91,16 @@ export class InviteToSharedSubscription implements UseCaseInterface {
}), }),
) )
await this.domainEventPublisher.publish(
this.domainEventFactory.createEmailRequestedEvent({
userEmail: dto.inviteeIdentifier,
level: EmailLevel.LEVELS.System,
body: getBody(dto.inviterEmail, savedInvitation.uuid),
messageIdentifier: 'SHARED_SUBSCRIPTION_INVITATION',
subject: getSubject(),
}),
)
return { return {
success: true, success: true,
sharedSubscriptionInvitationUuid: savedInvitation.uuid, sharedSubscriptionInvitationUuid: savedInvitation.uuid,
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.10.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.9.0...@standardnotes/domain-core@1.10.0) (2022-12-15)
### Features
* **domain-core:** add session model ([1c4d4c5](https://github.com/standardnotes/server/commit/1c4d4c57dea1187dc130f1ae8b7dc8ede332320f))
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.8.0...@standardnotes/domain-core@1.9.0) (2022-12-07) # [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.8.0...@standardnotes/domain-core@1.9.0) (2022-12-07)
### Features ### Features
+1 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-core", "name": "@standardnotes/domain-core",
"version": "1.9.0", "version": "1.10.0",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
@@ -23,10 +23,6 @@
"test": "jest spec --coverage --passWithNoTests" "test": "jest spec --coverage --passWithNoTests"
}, },
"dependencies": { "dependencies": {
"@standardnotes/common": "workspace:*",
"@standardnotes/features": "^1.52.1",
"@standardnotes/predicates": "workspace:*",
"@standardnotes/security": "workspace:*",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"shallow-equal-object": "^1.1.1", "shallow-equal-object": "^1.1.1",
"uuid": "^9.0.0" "uuid": "^9.0.0"
@@ -0,0 +1,26 @@
import { Session } from './Session'
import { SessionToken } from './SessionToken'
describe('Session', () => {
it('should create a session value object', () => {
const accessToken = SessionToken.create('foobar1', 1234567890).getValue()
const refreshToken = SessionToken.create('foobar2', 1234567890).getValue()
const valueOrError = Session.create(accessToken, refreshToken)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().accessToken.value).toEqual('foobar1')
expect(valueOrError.getValue().refreshToken.value).toEqual('foobar2')
expect(valueOrError.getValue().isReadOnly()).toEqual(false)
})
it('should create a session reado-only value object', () => {
const accessToken = SessionToken.create('foobar', 1234567890).getValue()
const refreshToken = SessionToken.create('foobar', 1234567890).getValue()
const valueOrError = Session.create(accessToken, refreshToken, true)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().isReadOnly()).toEqual(true)
})
})
@@ -0,0 +1,26 @@
import { ValueObject } from '../Core/ValueObject'
import { Result } from '../Core/Result'
import { SessionProps } from './SessionProps'
import { SessionToken } from './SessionToken'
export class Session extends ValueObject<SessionProps> {
get accessToken(): SessionToken {
return this.props.accessToken
}
get refreshToken(): SessionToken {
return this.props.refreshToken
}
isReadOnly(): boolean {
return this.props.readonlyAccess || false
}
private constructor(props: SessionProps) {
super(props)
}
static create(accessToken: SessionToken, refreshToken: SessionToken, readonlyAccess?: boolean): Result<Session> {
return Result.ok<Session>(new Session({ accessToken, refreshToken, readonlyAccess }))
}
}
@@ -0,0 +1,7 @@
import { SessionToken } from './SessionToken'
export interface SessionProps {
accessToken: SessionToken
refreshToken: SessionToken
readonlyAccess?: boolean
}
@@ -0,0 +1,21 @@
import { SessionToken } from './SessionToken'
describe('SessionToken', () => {
it('should create a value object', () => {
const valueOrError = SessionToken.create('foobar', 1234567890)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).toEqual('foobar')
expect(valueOrError.getValue().expiresAt).toEqual(1234567890)
})
it('should not create an invalid value object', () => {
let valueOrError = SessionToken.create('', 1234567890)
expect(valueOrError.isFailed()).toBeTruthy()
valueOrError = SessionToken.create('foobar', undefined as unknown as number)
expect(valueOrError.isFailed()).toBeTruthy()
})
})
@@ -0,0 +1,29 @@
import { ValueObject } from '../Core/ValueObject'
import { Result } from '../Core/Result'
import { SessionTokenProps } from './SessionTokenProps'
import { Validator } from '../Core/Validator'
export class SessionToken extends ValueObject<SessionTokenProps> {
get value(): string {
return this.props.value
}
get expiresAt(): number {
return this.props.expiresAt
}
private constructor(props: SessionTokenProps) {
super(props)
}
static create(value: string, expiresAt: number): Result<SessionToken> {
if (Validator.isNotEmpty(value).isFailed()) {
return Result.fail<SessionToken>('Could not create session token. Token value is empty')
}
if (Validator.isNotEmpty(expiresAt).isFailed()) {
return Result.fail<SessionToken>('Could not create session token. Token expiration is empty')
}
return Result.ok<SessionToken>(new SessionToken({ value, expiresAt }))
}
}
@@ -0,0 +1,4 @@
export interface SessionTokenProps {
value: string
expiresAt: number
}
+5
View File
@@ -1,3 +1,8 @@
export * from './Auth/Session'
export * from './Auth/SessionProps'
export * from './Auth/SessionToken'
export * from './Auth/SessionTokenProps'
export * from './Common/Dates' export * from './Common/Dates'
export * from './Common/DatesProps' export * from './Common/DatesProps'
export * from './Common/Email' export * from './Common/Email'
+44
View File
@@ -3,6 +3,50 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.56](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.55...@standardnotes/domain-events-infra@1.9.56) (2022-12-12)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.55](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.54...@standardnotes/domain-events-infra@1.9.55) (2022-12-12)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.54](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.53...@standardnotes/domain-events-infra@1.9.54) (2022-12-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.53](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.52...@standardnotes/domain-events-infra@1.9.53) (2022-12-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.52](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.51...@standardnotes/domain-events-infra@1.9.52) (2022-12-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.51](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.50...@standardnotes/domain-events-infra@1.9.51) (2022-12-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.50](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.49...@standardnotes/domain-events-infra@1.9.50) (2022-12-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.49](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.48...@standardnotes/domain-events-infra@1.9.49) (2022-12-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.48](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.47...@standardnotes/domain-events-infra@1.9.48) (2022-12-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.47](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.46...@standardnotes/domain-events-infra@1.9.47) (2022-12-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.46](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.45...@standardnotes/domain-events-infra@1.9.46) (2022-12-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.45](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.44...@standardnotes/domain-events-infra@1.9.45) (2022-12-08) ## [1.9.45](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.44...@standardnotes/domain-events-infra@1.9.45) (2022-12-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra **Note:** Version bump only for package @standardnotes/domain-events-infra
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-events-infra", "name": "@standardnotes/domain-events-infra",
"version": "1.9.45", "version": "1.9.56",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
+64
View File
@@ -3,6 +3,70 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.104.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.104.0...@standardnotes/domain-events@2.104.1) (2022-12-12)
### Bug Fixes
* **domain-events:** add additional domain event services ([2980c42](https://github.com/standardnotes/server/commit/2980c42e88b6be5f065c91c86bf85a706975f801))
# [2.104.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.103.2...@standardnotes/domain-events@2.104.0) (2022-12-12)
### Features
* **domain-events:** add event for email subscription unsubscribed ([7f18fcf](https://github.com/standardnotes/server/commit/7f18fcfc139911620f2ea72729357aefd0613315))
## [2.103.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.103.1...@standardnotes/domain-events@2.103.2) (2022-12-09)
**Note:** Version bump only for package @standardnotes/domain-events
## [2.103.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.103.0...@standardnotes/domain-events@2.103.1) (2022-12-09)
### Bug Fixes
* **domain-events:** add additional styles option for sending email ([20d9214](https://github.com/standardnotes/server/commit/20d92149a8c559edf6aa25932b3dbcbc00b2e878))
# [2.103.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.102.0...@standardnotes/domain-events@2.103.0) (2022-12-09)
### Features
* **workspace:** replace workspace invite created event with email requested ([61c1cff](https://github.com/standardnotes/server/commit/61c1cfff4bcee09e1f933cb3e085412b6f07cc42))
# [2.102.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.101.0...@standardnotes/domain-events@2.102.0) (2022-12-09)
### Features
* **syncing-server:** replace email backup attachment created with email requested ([32601f3](https://github.com/standardnotes/server/commit/32601f34f181b29b7c62cd2926111a0887d97fbf))
# [2.101.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.100.0...@standardnotes/domain-events@2.101.0) (2022-12-09)
### Features
* **syncing-server:** replace one drive backup failed event with email requested ([130f90b](https://github.com/standardnotes/server/commit/130f90bdb6cc88e073b9380e8aed5eebe8c49c1e))
# [2.100.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.99.0...@standardnotes/domain-events@2.100.0) (2022-12-09)
### Features
* **syncing-server:** remove google drive backup failed event in favour of email requested ([00fe321](https://github.com/standardnotes/server/commit/00fe32136e7add627e58e8ea223f7f484f1d3718))
# [2.99.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.4...@standardnotes/domain-events@2.99.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))
## [2.98.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.3...@standardnotes/domain-events@2.98.4) (2022-12-09)
### Bug Fixes
* **domain-events:** remove unused event ([cc4b4f9](https://github.com/standardnotes/server/commit/cc4b4f9bf831b9aabec7d506d977ee1df50d5222))
## [2.98.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.2...@standardnotes/domain-events@2.98.3) (2022-12-08)
### Bug Fixes
* **domain-events:** remove unused event ([cffc1f4](https://github.com/standardnotes/server/commit/cffc1f442f3c6f781c4468ac96245e13f57115d5))
## [2.98.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.1...@standardnotes/domain-events@2.98.2) (2022-12-08) ## [2.98.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.1...@standardnotes/domain-events@2.98.2) (2022-12-08)
### Bug Fixes ### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-events", "name": "@standardnotes/domain-events",
"version": "2.98.2", "version": "2.104.1",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
@@ -1,7 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { DailyAnalyticsReportGeneratedEventPayload } from './DailyAnalyticsReportGeneratedEventPayload'
export interface DailyAnalyticsReportGeneratedEvent extends DomainEventInterface {
type: 'DAILY_ANALYTICS_REPORT_GENERATED'
payload: DailyAnalyticsReportGeneratedEventPayload
}
@@ -1,41 +0,0 @@
export interface DailyAnalyticsReportGeneratedEventPayload {
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
averageCustomersCount: number
existingCustomersChurn: number
newCustomersChurn: number
periodKey: string
}>
}
}
@@ -10,4 +10,7 @@ export enum DomainEventService {
Scheduler = 'scheduler', Scheduler = 'scheduler',
Workspace = 'workspace', Workspace = 'workspace',
Analytics = 'analytics', Analytics = 'analytics',
Revisions = 'revisions',
Email = 'email',
Settings = 'settings',
} }
@@ -1,8 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { DropboxBackupFailedEventPayload } from './DropboxBackupFailedEventPayload'
export interface DropboxBackupFailedEvent extends DomainEventInterface {
type: 'DROPBOX_BACKUP_FAILED'
payload: DropboxBackupFailedEventPayload
}
@@ -1,5 +0,0 @@
export interface DropboxBackupFailedEventPayload {
muteCloudEmailsSettingUuid: string
extensionSettingUuid?: string
email: string
}
@@ -1,7 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { EmailBackupAttachmentCreatedEventPayload } from './EmailBackupAttachmentCreatedEventPayload'
export interface EmailBackupAttachmentCreatedEvent extends DomainEventInterface {
type: 'EMAIL_BACKUP_ATTACHMENT_CREATED'
payload: EmailBackupAttachmentCreatedEventPayload
}
@@ -1,6 +0,0 @@
export interface EmailBackupAttachmentCreatedEventPayload {
backupFileName: string
backupFileIndex: number
backupFilesTotal: number
email: string
}
@@ -4,6 +4,8 @@ export interface EmailRequestedEventPayload {
level: string level: string
subject: string subject: string
body: string body: string
sender?: string
additionalStyles?: string
attachments?: Array<{ attachments?: Array<{
filePath: string filePath: string
fileName: string fileName: string
@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { EmailSubscriptionUnsubscribedEventPayload } from './EmailSubscriptionUnsubscribedEventPayload'
export interface EmailSubscriptionUnsubscribedEvent extends DomainEventInterface {
type: 'EMAIL_SUBSCRIPTION_UNSUBSCRIBED'
payload: EmailSubscriptionUnsubscribedEventPayload
}
@@ -0,0 +1,4 @@
export interface EmailSubscriptionUnsubscribedEventPayload {
userEmail: string
level: string
}
@@ -1,8 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { GoogleDriveBackupFailedEventPayload } from './GoogleDriveBackupFailedEventPayload'
export interface GoogleDriveBackupFailedEvent extends DomainEventInterface {
type: 'GOOGLE_DRIVE_BACKUP_FAILED'
payload: GoogleDriveBackupFailedEventPayload
}
@@ -1,5 +0,0 @@
export interface GoogleDriveBackupFailedEventPayload {
muteCloudEmailsSettingUuid: string
extensionSettingUuid?: string
email: string
}
@@ -1,8 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { OfflineSubscriptionTokenCreatedEventPayload } from './OfflineSubscriptionTokenCreatedEventPayload'
export interface OfflineSubscriptionTokenCreatedEvent extends DomainEventInterface {
type: 'OFFLINE_SUBSCRIPTION_TOKEN_CREATED'
payload: OfflineSubscriptionTokenCreatedEventPayload
}
@@ -1,4 +0,0 @@
export interface OfflineSubscriptionTokenCreatedEventPayload {
token: string
email: string
}
@@ -1,8 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { OneDriveBackupFailedEventPayload } from './OneDriveBackupFailedEventPayload'
export interface OneDriveBackupFailedEvent extends DomainEventInterface {
type: 'ONE_DRIVE_BACKUP_FAILED'
payload: OneDriveBackupFailedEventPayload
}
@@ -1,5 +0,0 @@
export interface OneDriveBackupFailedEventPayload {
muteCloudEmailsSettingUuid: string
extensionSettingUuid?: string
email: string
}
@@ -1,8 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { RefundRequestedEventPayload } from './RefundRequestedEventPayload'
export interface RefundRequestedEvent extends DomainEventInterface {
type: 'REFUND_REQUESTED'
payload: RefundRequestedEventPayload
}
@@ -1,4 +0,0 @@
export interface RefundRequestedEventPayload {
userEmail: string
refundProcessingLink: string
}
@@ -1,8 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { SubscriptionRateAdjustedEventPayload } from './SubscriptionRateAdjustedEventPayload'
export interface SubscriptionRateAdjustedEvent extends DomainEventInterface {
type: 'SUBSCRIPTION_RATE_ADJUSTED'
payload: SubscriptionRateAdjustedEventPayload
}
@@ -1,5 +0,0 @@
export interface SubscriptionRateAdjustedEventPayload {
userEmail: string
newRateFormatted: string
refundAmountInDollarsFormatted: string
}
@@ -1,7 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { WorkspaceInviteCreatedEventPayload } from './WorkspaceInviteCreatedEventPayload'
export interface WorkspaceInviteCreatedEvent extends DomainEventInterface {
type: 'WORKSPACE_INVITE_CREATED'
payload: WorkspaceInviteCreatedEventPayload
}
@@ -1,6 +0,0 @@
export interface WorkspaceInviteCreatedEventPayload {
inviterUuid: string
inviteeEmail: string
inviteUuid: string
workspaceUuid: string
}
+2 -18
View File
@@ -2,26 +2,22 @@ export * from './Event/AccountDeletionRequestedEvent'
export * from './Event/AccountDeletionRequestedEventPayload' export * from './Event/AccountDeletionRequestedEventPayload'
export * from './Event/CloudBackupRequestedEvent' export * from './Event/CloudBackupRequestedEvent'
export * from './Event/CloudBackupRequestedEventPayload' export * from './Event/CloudBackupRequestedEventPayload'
export * from './Event/DailyAnalyticsReportGeneratedEvent'
export * from './Event/DailyAnalyticsReportGeneratedEventPayload'
export * from './Event/DiscountApplyRequestedEvent' export * from './Event/DiscountApplyRequestedEvent'
export * from './Event/DiscountApplyRequestedEventPayload' export * from './Event/DiscountApplyRequestedEventPayload'
export * from './Event/DiscountWithdrawRequestedEvent' export * from './Event/DiscountWithdrawRequestedEvent'
export * from './Event/DiscountWithdrawRequestedEventPayload' export * from './Event/DiscountWithdrawRequestedEventPayload'
export * from './Event/DomainEventInterface' export * from './Event/DomainEventInterface'
export * from './Event/DomainEventService' export * from './Event/DomainEventService'
export * from './Event/DropboxBackupFailedEvent'
export * from './Event/DropboxBackupFailedEventPayload'
export * from './Event/DuplicateItemSyncedEvent' export * from './Event/DuplicateItemSyncedEvent'
export * from './Event/DuplicateItemSyncedEventPayload' export * from './Event/DuplicateItemSyncedEventPayload'
export * from './Event/EmailArchiveExtensionSyncedEvent' export * from './Event/EmailArchiveExtensionSyncedEvent'
export * from './Event/EmailArchiveExtensionSyncedEventPayload' export * from './Event/EmailArchiveExtensionSyncedEventPayload'
export * from './Event/EmailBackupAttachmentCreatedEvent'
export * from './Event/EmailBackupAttachmentCreatedEventPayload'
export * from './Event/EmailBackupRequestedEvent' export * from './Event/EmailBackupRequestedEvent'
export * from './Event/EmailBackupRequestedEventPayload' export * from './Event/EmailBackupRequestedEventPayload'
export * from './Event/EmailRequestedEvent' export * from './Event/EmailRequestedEvent'
export * from './Event/EmailRequestedEventPayload' export * from './Event/EmailRequestedEventPayload'
export * from './Event/EmailSubscriptionUnsubscribedEvent'
export * from './Event/EmailSubscriptionUnsubscribedEventPayload'
export * from './Event/ExitDiscountAppliedEvent' export * from './Event/ExitDiscountAppliedEvent'
export * from './Event/ExitDiscountAppliedEventPayload' export * from './Event/ExitDiscountAppliedEventPayload'
export * from './Event/ExitDiscountApplyRequestedEvent' export * from './Event/ExitDiscountApplyRequestedEvent'
@@ -34,8 +30,6 @@ export * from './Event/FileRemovedEvent'
export * from './Event/FileRemovedEventPayload' export * from './Event/FileRemovedEventPayload'
export * from './Event/FileUploadedEvent' export * from './Event/FileUploadedEvent'
export * from './Event/FileUploadedEventPayload' export * from './Event/FileUploadedEventPayload'
export * from './Event/GoogleDriveBackupFailedEvent'
export * from './Event/GoogleDriveBackupFailedEventPayload'
export * from './Event/ItemDumpedEvent' export * from './Event/ItemDumpedEvent'
export * from './Event/ItemDumpedEventPayload' export * from './Event/ItemDumpedEventPayload'
export * from './Event/ItemRevisionCreationRequestedEvent' export * from './Event/ItemRevisionCreationRequestedEvent'
@@ -50,10 +44,6 @@ export * from './Event/ListedAccountRequestedEvent'
export * from './Event/ListedAccountRequestedEventPayload' export * from './Event/ListedAccountRequestedEventPayload'
export * from './Event/MuteEmailsSettingChangedEvent' export * from './Event/MuteEmailsSettingChangedEvent'
export * from './Event/MuteEmailsSettingChangedEventPayload' export * from './Event/MuteEmailsSettingChangedEventPayload'
export * from './Event/OfflineSubscriptionTokenCreatedEvent'
export * from './Event/OfflineSubscriptionTokenCreatedEventPayload'
export * from './Event/OneDriveBackupFailedEvent'
export * from './Event/OneDriveBackupFailedEventPayload'
export * from './Event/PaymentFailedEvent' export * from './Event/PaymentFailedEvent'
export * from './Event/PaymentFailedEventPayload' export * from './Event/PaymentFailedEventPayload'
export * from './Event/PaymentSuccessEvent' export * from './Event/PaymentSuccessEvent'
@@ -62,8 +52,6 @@ export * from './Event/PredicateVerificationRequestedEvent'
export * from './Event/PredicateVerificationRequestedEventPayload' export * from './Event/PredicateVerificationRequestedEventPayload'
export * from './Event/PredicateVerifiedEvent' export * from './Event/PredicateVerifiedEvent'
export * from './Event/PredicateVerifiedEventPayload' export * from './Event/PredicateVerifiedEventPayload'
export * from './Event/RefundRequestedEvent'
export * from './Event/RefundRequestedEventPayload'
export * from './Event/RefundProcessedEvent' export * from './Event/RefundProcessedEvent'
export * from './Event/RefundProcessedEventPayload' export * from './Event/RefundProcessedEventPayload'
export * from './Event/RevisionsCopyRequestedEvent' export * from './Event/RevisionsCopyRequestedEvent'
@@ -78,8 +66,6 @@ export * from './Event/SubscriptionCancelledEvent'
export * from './Event/SubscriptionCancelledEventPayload' export * from './Event/SubscriptionCancelledEventPayload'
export * from './Event/SubscriptionPurchasedEvent' export * from './Event/SubscriptionPurchasedEvent'
export * from './Event/SubscriptionPurchasedEventPayload' export * from './Event/SubscriptionPurchasedEventPayload'
export * from './Event/SubscriptionRateAdjustedEvent'
export * from './Event/SubscriptionRateAdjustedEventPayload'
export * from './Event/SubscriptionReactivatedEvent' export * from './Event/SubscriptionReactivatedEvent'
export * from './Event/SubscriptionReactivatedEventPayload' export * from './Event/SubscriptionReactivatedEventPayload'
export * from './Event/SubscriptionReassignedEvent' export * from './Event/SubscriptionReassignedEvent'
@@ -108,8 +94,6 @@ export * from './Event/WebSocketMessageRequestedEvent'
export * from './Event/WebSocketMessageRequestedEventPayload' export * from './Event/WebSocketMessageRequestedEventPayload'
export * from './Event/WorkspaceInviteAcceptedEvent' export * from './Event/WorkspaceInviteAcceptedEvent'
export * from './Event/WorkspaceInviteAcceptedEventPayload' export * from './Event/WorkspaceInviteAcceptedEventPayload'
export * from './Event/WorkspaceInviteCreatedEvent'
export * from './Event/WorkspaceInviteCreatedEventPayload'
export * from './Handler/DomainEventHandlerInterface' export * from './Handler/DomainEventHandlerInterface'
export * from './Handler/DomainEventMessageHandlerInterface' export * from './Handler/DomainEventMessageHandlerInterface'
+44
View File
@@ -3,6 +3,50 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.53](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.52...@standardnotes/event-store@1.6.53) (2022-12-12)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.52](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.51...@standardnotes/event-store@1.6.52) (2022-12-12)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.51](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.50...@standardnotes/event-store@1.6.51) (2022-12-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.50](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.49...@standardnotes/event-store@1.6.50) (2022-12-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.49](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.48...@standardnotes/event-store@1.6.49) (2022-12-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.48](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.47...@standardnotes/event-store@1.6.48) (2022-12-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.47](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.46...@standardnotes/event-store@1.6.47) (2022-12-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.46](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.45...@standardnotes/event-store@1.6.46) (2022-12-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.45](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.44...@standardnotes/event-store@1.6.45) (2022-12-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.44](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.43...@standardnotes/event-store@1.6.44) (2022-12-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.43](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.42...@standardnotes/event-store@1.6.43) (2022-12-08)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.42](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.41...@standardnotes/event-store@1.6.42) (2022-12-08) ## [1.6.42](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.41...@standardnotes/event-store@1.6.42) (2022-12-08)
**Note:** Version bump only for package @standardnotes/event-store **Note:** Version bump only for package @standardnotes/event-store
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/event-store", "name": "@standardnotes/event-store",
"version": "1.6.42", "version": "1.6.53",
"description": "Event Store Service", "description": "Event Store Service",
"private": true, "private": true,
"main": "dist/src/index.js", "main": "dist/src/index.js",
+44
View File
@@ -3,6 +3,50 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.52](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.51...@standardnotes/files-server@1.8.52) (2022-12-12)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.51](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.50...@standardnotes/files-server@1.8.51) (2022-12-12)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.50](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.49...@standardnotes/files-server@1.8.50) (2022-12-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.49](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.48...@standardnotes/files-server@1.8.49) (2022-12-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.48](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.47...@standardnotes/files-server@1.8.48) (2022-12-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.47](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.46...@standardnotes/files-server@1.8.47) (2022-12-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.46](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.45...@standardnotes/files-server@1.8.46) (2022-12-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.45](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.44...@standardnotes/files-server@1.8.45) (2022-12-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.44](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.43...@standardnotes/files-server@1.8.44) (2022-12-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.43](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.42...@standardnotes/files-server@1.8.43) (2022-12-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.42](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.41...@standardnotes/files-server@1.8.42) (2022-12-08)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.41](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.40...@standardnotes/files-server@1.8.41) (2022-12-08) ## [1.8.41](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.40...@standardnotes/files-server@1.8.41) (2022-12-08)
**Note:** Version bump only for package @standardnotes/files-server **Note:** Version bump only for package @standardnotes/files-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/files-server", "name": "@standardnotes/files-server",
"version": "1.8.41", "version": "1.8.52",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
+54
View File
@@ -3,6 +3,60 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.27](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.26...@standardnotes/revisions-server@1.9.27) (2022-12-15)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.26](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.25...@standardnotes/revisions-server@1.9.26) (2022-12-12)
### Bug Fixes
* **revisions:** responses to match previous response structure ([530a426](https://github.com/standardnotes/server/commit/530a42660157b63d034cd2228fc83a3fcce921e0))
## [1.9.25](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.24...@standardnotes/revisions-server@1.9.25) (2022-12-12)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.24](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.23...@standardnotes/revisions-server@1.9.24) (2022-12-12)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.23](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.22...@standardnotes/revisions-server@1.9.23) (2022-12-09)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.22](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.21...@standardnotes/revisions-server@1.9.22) (2022-12-09)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.21](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.20...@standardnotes/revisions-server@1.9.21) (2022-12-09)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.20](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.19...@standardnotes/revisions-server@1.9.20) (2022-12-09)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.19](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.18...@standardnotes/revisions-server@1.9.19) (2022-12-09)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.18](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.17...@standardnotes/revisions-server@1.9.18) (2022-12-09)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.17](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.16...@standardnotes/revisions-server@1.9.17) (2022-12-09)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.16](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.15...@standardnotes/revisions-server@1.9.16) (2022-12-09)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.15](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.14...@standardnotes/revisions-server@1.9.15) (2022-12-08)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.14](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.13...@standardnotes/revisions-server@1.9.14) (2022-12-08) ## [1.9.14](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.13...@standardnotes/revisions-server@1.9.14) (2022-12-08)
**Note:** Version bump only for package @standardnotes/revisions-server **Note:** Version bump only for package @standardnotes/revisions-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/revisions-server", "name": "@standardnotes/revisions-server",
"version": "1.9.14", "version": "1.9.27",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
@@ -2,11 +2,13 @@ import { Logger } from 'winston'
import { HttpResponse, HttpStatusCode } from '@standardnotes/api' import { HttpResponse, HttpStatusCode } from '@standardnotes/api'
import { GetRevisionsMetada } from '../Domain/UseCase/GetRevisionsMetada/GetRevisionsMetada' import { GetRevisionsMetada } from '../Domain/UseCase/GetRevisionsMetada/GetRevisionsMetada'
import { GetRevisionsMetadataRequestParams } from '../Infra/Http/GetRevisionsMetadataRequestParams' import { GetRevisionsMetadataRequestParams } from '../Infra/Http/Request/GetRevisionsMetadataRequestParams'
import { GetRevisionRequestParams } from '../Infra/Http/GetRevisionRequestParams' import { GetRevisionRequestParams } from '../Infra/Http/Request/GetRevisionRequestParams'
import { DeleteRevisionRequestParams } from '../Infra/Http/Request/DeleteRevisionRequestParams'
import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision' import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision'
import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision' import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision'
import { DeleteRevisionRequestParams } from '../Infra/Http/DeleteRevisionRequestParams' import { GetRevisionsMetadataResponse } from '../Infra/Http/Response/GetRevisionsMetadataResponse'
import { GetRevisionResponse } from '../Infra/Http/Response/GetRevisionResponse'
export class RevisionsController { export class RevisionsController {
constructor( constructor(
@@ -16,7 +18,7 @@ export class RevisionsController {
private logger: Logger, private logger: Logger,
) {} ) {}
async getRevisions(params: GetRevisionsMetadataRequestParams): Promise<HttpResponse> { async getRevisions(params: GetRevisionsMetadataRequestParams): Promise<GetRevisionsMetadataResponse> {
const revisionMetadataOrError = await this.getRevisionsMetadata.execute({ const revisionMetadataOrError = await this.getRevisionsMetadata.execute({
itemUuid: params.itemUuid, itemUuid: params.itemUuid,
userUuid: params.userUuid, userUuid: params.userUuid,
@@ -41,7 +43,7 @@ export class RevisionsController {
} }
} }
async getRevision(params: GetRevisionRequestParams): Promise<HttpResponse> { async getRevision(params: GetRevisionRequestParams): Promise<GetRevisionResponse> {
const revisionOrError = await this.doGetRevision.execute({ const revisionOrError = await this.doGetRevision.execute({
revisionUuid: params.revisionUuid, revisionUuid: params.revisionUuid,
userUuid: params.userUuid, userUuid: params.userUuid,
@@ -0,0 +1,8 @@
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
import { Either } from '@standardnotes/common'
import { GetRevisionResponseBody } from './GetRevisionResponseBody'
export interface GetRevisionResponse extends HttpResponse {
data: Either<GetRevisionResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,5 @@
import { Revision } from '../../../Domain/Revision/Revision'
export interface GetRevisionResponseBody {
revision: Revision
}
@@ -0,0 +1,8 @@
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
import { Either } from '@standardnotes/common'
import { GetRevisionsMetadataResponseBody } from './GetRevisionsMetadataResponseBody'
export interface GetRevisionsMetadataResponse extends HttpResponse {
data: Either<GetRevisionsMetadataResponseBody, HttpErrorResponseBody>
}
@@ -0,0 +1,5 @@
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
export interface GetRevisionsMetadataResponseBody {
revisions: Array<RevisionMetadata>
}
@@ -18,7 +18,7 @@ export class InversifyExpressRevisionsController extends BaseHttpController {
userUuid: response.locals.user.uuid, userUuid: response.locals.user.uuid,
}) })
return this.json(result.data, result.status) return this.json(result.data.error ? result.data : result.data.revisions, result.status)
} }
@httpGet('/:uuid') @httpGet('/:uuid')
@@ -28,7 +28,7 @@ export class InversifyExpressRevisionsController extends BaseHttpController {
userUuid: response.locals.user.uuid, userUuid: response.locals.user.uuid,
}) })
return this.json(result.data, result.status) return this.json(result.data.error ? result.data : result.data.revision, result.status)
} }
@httpDelete('/:uuid') @httpDelete('/:uuid')
+50
View File
@@ -3,6 +3,56 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.15.7](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.6...@standardnotes/scheduler-server@1.15.7) (2022-12-15)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.15.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.5...@standardnotes/scheduler-server@1.15.6) (2022-12-12)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.15.5](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.4...@standardnotes/scheduler-server@1.15.5) (2022-12-12)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.15.4](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.3...@standardnotes/scheduler-server@1.15.4) (2022-12-09)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.15.3](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.2...@standardnotes/scheduler-server@1.15.3) (2022-12-09)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.15.2](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.1...@standardnotes/scheduler-server@1.15.2) (2022-12-09)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.15.1](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.0...@standardnotes/scheduler-server@1.15.1) (2022-12-09)
**Note:** Version bump only for package @standardnotes/scheduler-server
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.10...@standardnotes/scheduler-server@1.15.0) (2022-12-09)
### Features
* **syncing-server:** replace one drive backup failed event with email requested ([130f90b](https://github.com/standardnotes/server/commit/130f90bdb6cc88e073b9380e8aed5eebe8c49c1e))
## [1.14.10](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.9...@standardnotes/scheduler-server@1.14.10) (2022-12-09)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.14.9](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.8...@standardnotes/scheduler-server@1.14.9) (2022-12-09)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.14.8](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.7...@standardnotes/scheduler-server@1.14.8) (2022-12-09)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.14.7](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.6...@standardnotes/scheduler-server@1.14.7) (2022-12-08)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.14.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.5...@standardnotes/scheduler-server@1.14.6) (2022-12-08) ## [1.14.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.5...@standardnotes/scheduler-server@1.14.6) (2022-12-08)
**Note:** Version bump only for package @standardnotes/scheduler-server **Note:** Version bump only for package @standardnotes/scheduler-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/scheduler-server", "name": "@standardnotes/scheduler-server",
"version": "1.14.6", "version": "1.15.7",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
@@ -2,7 +2,7 @@ import {
DiscountApplyRequestedEvent, DiscountApplyRequestedEvent,
DiscountWithdrawRequestedEvent, DiscountWithdrawRequestedEvent,
DomainEventPublisherInterface, DomainEventPublisherInterface,
EmailMessageRequestedEvent, EmailRequestedEvent,
ExitDiscountWithdrawRequestedEvent, ExitDiscountWithdrawRequestedEvent,
} from '@standardnotes/domain-events' } from '@standardnotes/domain-events'
import { PredicateName } from '@standardnotes/predicates' import { PredicateName } from '@standardnotes/predicates'
@@ -45,9 +45,7 @@ describe('JobDoneInterpreter', () => {
predicateRepository.findByJobUuid = jest.fn().mockReturnValue([]) predicateRepository.findByJobUuid = jest.fn().mockReturnValue([])
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface> domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createEmailRequestedEvent = jest domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
.fn()
.mockReturnValue({} as jest.Mocked<EmailMessageRequestedEvent>)
domainEventFactory.createDiscountApplyRequestedEvent = jest domainEventFactory.createDiscountApplyRequestedEvent = jest
.fn() .fn()
.mockReturnValue({} as jest.Mocked<DiscountApplyRequestedEvent>) .mockReturnValue({} as jest.Mocked<DiscountApplyRequestedEvent>)
+150
View File
@@ -3,6 +3,156 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.5...@standardnotes/syncing-server@1.26.6) (2022-12-15)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.26.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.4...@standardnotes/syncing-server@1.26.5) (2022-12-15)
### Bug Fixes
* **syncing-server:** revisions processing limit ([f10fa83](https://github.com/standardnotes/syncing-server-js/commit/f10fa839fbe1baec32fd234b41d8cd42fc50931a))
## [1.26.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.3...@standardnotes/syncing-server@1.26.4) (2022-12-15)
### Bug Fixes
* **syncing-server:** user uuid field name ([bfe6f42](https://github.com/standardnotes/syncing-server-js/commit/bfe6f4255a2d3f6e7dfa5eab1509dd770d9bff18))
## [1.26.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.2...@standardnotes/syncing-server@1.26.3) (2022-12-15)
### Bug Fixes
* **syncing-server:** select fields in query for revisions ([ce53c45](https://github.com/standardnotes/syncing-server-js/commit/ce53c459e6ad0d469fcd0ebd7bf4caeb0e1d9c9c))
## [1.26.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.1...@standardnotes/syncing-server@1.26.2) (2022-12-14)
### Bug Fixes
* **syncing-server:** revisions procedure logs ([1e2b496](https://github.com/standardnotes/syncing-server-js/commit/1e2b496f4f87fd49ae8fba8ed9b76d3b6a2c31fa))
## [1.26.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.0...@standardnotes/syncing-server@1.26.1) (2022-12-14)
### Bug Fixes
* **syncing-server:** revisions procedure logs ([22fba8b](https://github.com/standardnotes/syncing-server-js/commit/22fba8ba806115b0f4bb4b083ae8595a3f0010b0))
# [1.26.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.6...@standardnotes/syncing-server@1.26.0) (2022-12-14)
### Features
* **syncing-server:** change revisions procedure to pagination instead of streaming ([4b1fe3b](https://github.com/standardnotes/syncing-server-js/commit/4b1fe3ba91594858e15cbdfbc21062c428dd03b4))
## [1.25.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.5...@standardnotes/syncing-server@1.25.6) (2022-12-14)
### Bug Fixes
* **syncing-server:** additional stream events handling on revisions procedure ([2ec28e5](https://github.com/standardnotes/syncing-server-js/commit/2ec28e541efa2bd9172431d45c5c1560692a912c))
## [1.25.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.4...@standardnotes/syncing-server@1.25.5) (2022-12-14)
### Bug Fixes
* **syncing-server:** revisions procedure with env var defined ranges ([9b27547](https://github.com/standardnotes/syncing-server-js/commit/9b27547dae1e5d5e6d071a069803e2bf3f8acdda))
## [1.25.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.3...@standardnotes/syncing-server@1.25.4) (2022-12-13)
### Bug Fixes
* **syncing-server:** logs on revisions procedure ([225e0aa](https://github.com/standardnotes/syncing-server-js/commit/225e0aaf88a396bf308c2e5eed0bb6e130cb2d64))
## [1.25.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.2...@standardnotes/syncing-server@1.25.3) (2022-12-13)
### Bug Fixes
* **syncing-server:** revisions ownership procedure destructured ([124c443](https://github.com/standardnotes/syncing-server-js/commit/124c4435285c2c2e8d0ce8b47907ebd47af27576))
## [1.25.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.1...@standardnotes/syncing-server@1.25.2) (2022-12-13)
### Bug Fixes
* **syncing-server:** change revisions migration to notes ([c419f1c](https://github.com/standardnotes/syncing-server-js/commit/c419f1ce220c27acabfc813a30b3edd6c4aadaa1))
## [1.25.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.0...@standardnotes/syncing-server@1.25.1) (2022-12-13)
### Bug Fixes
* **syncing-server:** revisions procedure properties ([cd101b9](https://github.com/standardnotes/syncing-server-js/commit/cd101b96eae8969a4dd2387deb1d4e8679ead216))
# [1.25.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.7...@standardnotes/syncing-server@1.25.0) (2022-12-12)
### Features
* **syncing-server:** fix streaming items for revisions update ([a55a995](https://github.com/standardnotes/syncing-server-js/commit/a55a9956602bee7dbb0f93f058aceff7a2136ffd))
## [1.24.7](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.6...@standardnotes/syncing-server@1.24.7) (2022-12-12)
### Bug Fixes
* **syncing-server:** revisions updating - select fields ([4ff8030](https://github.com/standardnotes/syncing-server-js/commit/4ff8030f8709ee18853c2e782cfc5d99c826f074))
## [1.24.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.5...@standardnotes/syncing-server@1.24.6) (2022-12-12)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.24.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.4...@standardnotes/syncing-server@1.24.5) (2022-12-12)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.24.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.3...@standardnotes/syncing-server@1.24.4) (2022-12-12)
### Bug Fixes
* **syncing-server:** data integrity check on revisions fix ([6368342](https://github.com/standardnotes/syncing-server-js/commit/6368342149d658898aef62651bfafddf51c26dbe))
## [1.24.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.2...@standardnotes/syncing-server@1.24.3) (2022-12-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.24.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.1...@standardnotes/syncing-server@1.24.2) (2022-12-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.24.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.0...@standardnotes/syncing-server@1.24.1) (2022-12-09)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.24.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.23.0...@standardnotes/syncing-server@1.24.0) (2022-12-09)
### Features
* **syncing-server:** replace email backup attachment created with email requested ([32601f3](https://github.com/standardnotes/syncing-server-js/commit/32601f34f181b29b7c62cd2926111a0887d97fbf))
# [1.23.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.22.0...@standardnotes/syncing-server@1.23.0) (2022-12-09)
### Features
* **syncing-server:** replace one drive backup failed event with email requested ([130f90b](https://github.com/standardnotes/syncing-server-js/commit/130f90bdb6cc88e073b9380e8aed5eebe8c49c1e))
# [1.22.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.21.0...@standardnotes/syncing-server@1.22.0) (2022-12-09)
### Features
* **syncing-serfver:** remove dropbox backup failed event in favour of email requested ([118156c](https://github.com/standardnotes/syncing-server-js/commit/118156c62de70eca8fd89414f6e409abd0363e62))
# [1.21.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.17...@standardnotes/syncing-server@1.21.0) (2022-12-09)
### Features
* **syncing-server:** remove google drive backup failed event in favour of email requested ([00fe321](https://github.com/standardnotes/syncing-server-js/commit/00fe32136e7add627e58e8ea223f7f484f1d3718))
## [1.20.17](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.16...@standardnotes/syncing-server@1.20.17) (2022-12-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.20.16](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.15...@standardnotes/syncing-server@1.20.16) (2022-12-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.20.15](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.14...@standardnotes/syncing-server@1.20.15) (2022-12-08)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.20.14](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.13...@standardnotes/syncing-server@1.20.14) (2022-12-08) ## [1.20.14](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.13...@standardnotes/syncing-server@1.20.14) (2022-12-08)
**Note:** Version bump only for package @standardnotes/syncing-server **Note:** Version bump only for package @standardnotes/syncing-server
+85 -27
View File
@@ -10,45 +10,82 @@ import { Env } from '../src/Bootstrap/Env'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events' import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { ItemRepositoryInterface } from '../src/Domain/Item/ItemRepositoryInterface' import { ItemRepositoryInterface } from '../src/Domain/Item/ItemRepositoryInterface'
import { Stream } from 'stream' import { ContentType } from '@standardnotes/common'
const fixRevisionsOwnership = async ( const fixRevisionsOwnership = async (
year: number,
month: number,
revisionsProcessingLimit: number,
itemRepository: ItemRepositoryInterface, itemRepository: ItemRepositoryInterface,
domainEventFactory: DomainEventFactoryInterface, domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface, domainEventPublisher: DomainEventPublisherInterface,
logger: Logger, logger: Logger,
): Promise<void> => { ): Promise<void> => {
const stream = await itemRepository.streamAll({ const createdAfter = new Date(`${year}-${month}-1`)
sortBy: 'updated_at_timestamp', const createdBefore = new Date(`${month !== 12 ? year : year + 1}-${month !== 12 ? month + 1 : 1}-1`)
logger.info(`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] Processing items`)
const itemsCount = await itemRepository.countAll({
createdBetween: [createdAfter, createdBefore],
selectFields: ['uuid', 'user_uuid'],
contentType: [ContentType.Note, ContentType.File],
sortOrder: 'ASC', sortOrder: 'ASC',
createdBefore: new Date('2022-11-23'), sortBy: 'uuid',
selectFields: ['user_uuid', 'item_uuid'],
}) })
return new Promise((resolve, reject) => { logger.info(
stream `[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] There are ${itemsCount} items to process.`,
.pipe( )
new Stream.Transform({
objectMode: true,
transform: async (rawItemData, _encoding, callback) => {
try {
await domainEventPublisher.publish(
domainEventFactory.createRevisionsOwnershipUpdateRequestedEvent({
userUuid: rawItemData.item_user_uuid,
itemUuid: rawItemData.item_uuid,
}),
)
} catch (error) {
logger.error(`Could not process item ${rawItemData.item_uuid}: ${(error as Error).message}`)
}
callback() const amountOfPages = Math.ceil(itemsCount / revisionsProcessingLimit)
}, const tenPercentOfPages = Math.ceil(amountOfPages / 10)
let itemsProcessedCounter = 0
let itemsSkippedCounter = 0
for (let page = 1; page <= amountOfPages; page++) {
if (page % tenPercentOfPages === 0) {
logger.info(
`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] Processing page ${page}/${amountOfPages} of items.`,
)
logger.info(
`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] Processed successfully/skipped items: ${itemsProcessedCounter}/${itemsSkippedCounter}.`,
)
}
const items = await itemRepository.findAll({
createdBetween: [createdAfter, createdBefore],
selectFields: ['uuid', 'user_uuid'],
contentType: [ContentType.Note, ContentType.File],
offset: (page - 1) * revisionsProcessingLimit,
limit: revisionsProcessingLimit,
sortOrder: 'ASC',
sortBy: 'uuid',
})
if (items.length === 0) {
logger.warn(
`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] No items fetched for offset ${
(page - 1) * revisionsProcessingLimit
} and limit ${revisionsProcessingLimit}.`,
)
}
for (const item of items) {
if (!item.userUuid || !item.uuid) {
itemsSkippedCounter++
continue
}
await domainEventPublisher.publish(
domainEventFactory.createRevisionsOwnershipUpdateRequestedEvent({
userUuid: item.userUuid,
itemUuid: item.uuid,
}), }),
) )
.on('finish', resolve)
.on('error', reject) itemsProcessedCounter++
}) }
}
} }
const container = new ContainerConfigLoader() const container = new ContainerConfigLoader()
@@ -64,7 +101,28 @@ void container.load().then((container) => {
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory) const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher) const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
Promise.resolve(fixRevisionsOwnership(itemRepository, domainEventFactory, domainEventPublisher, logger)) const years = env.get('REVISION_YEARS').split(',')
const months = env.get('REVISION_MONTHS').split(',')
const revisionsProcessingLimit = env.get('REVISIONS_PROCESSING_LIMIT')
const promises = []
for (const year of years) {
for (const month of months) {
promises.push(
fixRevisionsOwnership(
+year,
+month,
+revisionsProcessingLimit,
itemRepository,
domainEventFactory,
domainEventPublisher,
logger,
),
)
}
}
Promise.all(promises)
.then(() => { .then(() => {
logger.info('revisions ownership fix complete.') logger.info('revisions ownership fix complete.')
+6 -6
View File
@@ -5,33 +5,33 @@ COMMAND=$1 && shift 1
case "$COMMAND" in case "$COMMAND" in
'start-local') 'start-local')
echo "Starting Web in Local Mode..." echo "[Docker] Starting Web in Local Mode..."
yarn workspace @standardnotes/syncing-server start:local yarn workspace @standardnotes/syncing-server start:local
;; ;;
'start-web' ) 'start-web' )
echo "Starting Web..." echo "[Docker] Starting Web..."
yarn workspace @standardnotes/syncing-server start yarn workspace @standardnotes/syncing-server start
;; ;;
'start-worker' ) 'start-worker' )
echo "Starting Worker..." echo "[Docker] Starting Worker..."
yarn workspace @standardnotes/syncing-server worker yarn workspace @standardnotes/syncing-server worker
;; ;;
'content-size-recalculate' ) 'content-size-recalculate' )
echo "Starting Content Size Recalculation..." echo "[Docker] Starting Content Size Recalculation..."
USER_UUID=$1 && shift 1 USER_UUID=$1 && shift 1
yarn workspace @standardnotes/syncing-server content-size $USER_UUID yarn workspace @standardnotes/syncing-server content-size $USER_UUID
;; ;;
'revisions-ownership-fix' ) 'revisions-ownership-fix' )
echo "Starting Revisions Ownership Fixing..." echo "[Docker] Starting Revisions Ownership Fixing..."
yarn workspace @standardnotes/syncing-server revisions-ownership yarn workspace @standardnotes/syncing-server revisions-ownership
;; ;;
* ) * )
echo "Unknown command" echo "[Docker] Unknown command"
;; ;;
esac esac
+1 -1
View File
@@ -7,6 +7,6 @@ module.exports = {
transform: { transform: {
...tsjPreset.transform, ...tsjPreset.transform,
}, },
coveragePathIgnorePatterns: ['/Bootstrap/', 'HealthCheckController', '/Infra/'], coveragePathIgnorePatterns: ['/Bootstrap/', 'HealthCheckController', '/Infra/', '/Domain/Email/'],
setupFilesAfterEnv: ['./test-setup.ts'], setupFilesAfterEnv: ['./test-setup.ts'],
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/syncing-server", "name": "@standardnotes/syncing-server",
"version": "1.20.14", "version": "1.26.6",
"engines": { "engines": {
"node": ">=18.0.0 <19.0.0" "node": ">=18.0.0 <19.0.0"
}, },
@@ -0,0 +1,9 @@
import { html } from './dropbox-backup-failed.html'
export function getSubject(): string {
return 'Failed Daily Backup to Dropbox'
}
export function getBody(): string {
return html
}
@@ -0,0 +1,14 @@
import { html } from './email-backup-attachment-created.html'
export function getSubject(fileIndex: number, numberOfFiles: number, date: string): string {
let subject = `Data Backup for ${date}`
if (numberOfFiles > 1) {
subject = `Data Backup for ${date} - Part ${fileIndex} Of ${numberOfFiles}`
}
return subject
}
export function getBody(email: string): string {
return html(email)
}

Some files were not shown because too many files have changed in this diff Show More