mirror of
https://github.com/standardnotes/server
synced 2026-04-19 17:02:25 -04:00
Compare commits
279 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55f8f65c3f | |||
| 3953dbc6b4 | |||
| 0b205287d1 | |||
| 4f0bc57b1a | |||
| 7d43316597 | |||
| 65d31f011b | |||
| 80dd6efae3 | |||
| a96f2c9153 | |||
| 225e0aaf88 | |||
| f0c85910bc | |||
| 124c443528 | |||
| 37c7f8d39f | |||
| c419f1ce22 | |||
| 4949cdfe2f | |||
| cd101b96ea | |||
| 40d0e4631f | |||
| a55a995660 | |||
| 1d576d48ad | |||
| 4ff8030f87 | |||
| c15e2e2c8f | |||
| 41d31a8d75 | |||
| 10e2a26352 | |||
| 6e547f77d0 | |||
| 530a426601 | |||
| 642d6bab77 | |||
| 7980af3d82 | |||
| 2980c42e88 | |||
| b03994f9db | |||
| 41906ec2f9 | |||
| 4d1e7ff2a5 | |||
| 7f18fcfc13 | |||
| ff02ce0747 | |||
| a6056600eb | |||
| 24c94326d5 | |||
| 48c0cb5e62 | |||
| 9968efe1b2 | |||
| 6368342149 | |||
| b5f73db210 | |||
| 22d6a02d04 | |||
| 4e0bcfcccf | |||
| 104313c15d | |||
| 814289af46 | |||
| 3096cd98d5 | |||
| 45dfefbc7a | |||
| 20d92149a8 | |||
| 9c01fffca5 | |||
| 61c1cfff4b | |||
| 7e74261f62 | |||
| 32601f34f1 | |||
| aef69a1a96 | |||
| 130f90bdb6 | |||
| 851c7de87f | |||
| 118156c62d | |||
| cdad3143c9 | |||
| 00fe32136e | |||
| 52f56eeb68 | |||
| b595264e31 | |||
| bf04262170 | |||
| fd589922bb | |||
| fb7029f5c1 | |||
| cc4b4f9bf8 | |||
| b048d6d7e3 | |||
| cffc1f442f | |||
| 91fe710741 | |||
| 5a1eb9fdac | |||
| a64ef6e750 | |||
| 47d2012b3d | |||
| 2c99cd2e21 | |||
| 435cd2f66a | |||
| 372b12dfc2 | |||
| 3a12f5c1c4 | |||
| 781de224b6 | |||
| eff09454c3 | |||
| 473feba6a8 | |||
| e9f0704fb0 | |||
| 8c99469d88 | |||
| 8ec1311dfc | |||
| e48cca6b45 | |||
| d660721f95 | |||
| c52bb93d79 | |||
| ffb6bfd0c9 | |||
| 6e0855f9b3 | |||
| ec9e9ec387 | |||
| fa75aa40f0 | |||
| b865953c22 | |||
| 2542cf6f9a | |||
| cb9499b87f | |||
| c351f01f67 | |||
| c87561fca7 | |||
| a363c143fa | |||
| fb81d2b926 | |||
| 05b1b8f079 | |||
| 7848dc06d4 | |||
| 3a005719b7 | |||
| 6928988f78 | |||
| a521894d7c | |||
| b7fb1d9c08 | |||
| 5f67e45911 | |||
| fddf9fccbd | |||
| 2bedbd7bd2 | |||
| 02f3c85796 | |||
| 3b5bd6a47f | |||
| 06fd404d44 | |||
| d931c52508 | |||
| 800fe9e4c8 | |||
| 8b3d78678f | |||
| 2351cd3ad6 | |||
| dd86c5bcdf | |||
| d0c00e306e | |||
| 6cd68ddd6a | |||
| 02639cddb2 | |||
| 0f67aa4058 | |||
| 78c3403d5f | |||
| fc8f8c574d | |||
| 3972ee580d | |||
| b0a994d5be | |||
| 80df28a0c4 | |||
| 1c6c6a9296 | |||
| 7bb698e442 | |||
| 784728cd54 | |||
| 4b883b68de | |||
| dec2cc2aaf | |||
| b4e8971ad2 | |||
| 84e436265e | |||
| ac8a69f8d4 | |||
| b912e050ea | |||
| 284561d093 | |||
| efc355982c | |||
| 8907879a19 | |||
| 86f6057207 | |||
| 4c92698c73 | |||
| 8407c3b649 | |||
| ed8f82617d | |||
| 31d040d1b6 | |||
| 25a6796e63 | |||
| ff091918aa | |||
| 91b76edce1 | |||
| 5ae5c83bf5 | |||
| 9d90f276de | |||
| 245f091e22 | |||
| ae2f8f086b | |||
| 5e5eb7f937 | |||
| 748630e1f1 | |||
| 43064c8c55 | |||
| 4559a3047c | |||
| de8064ee5c | |||
| 48c8dba342 | |||
| 31a515b2f1 | |||
| 294f56e189 | |||
| 70596a0aac | |||
| 74bc79116b | |||
| e6bd50ae77 | |||
| 308662550f | |||
| d94a7e7157 | |||
| 630b264754 | |||
| 5f2be44b85 | |||
| f68ece68af | |||
| 70c829a2c9 | |||
| e3b6ac4874 | |||
| a762d5a22c | |||
| 3686a26019 | |||
| 80daec748d | |||
| 94359f1299 | |||
| 59dda1bb99 | |||
| 806a732cbc | |||
| 7816be7ba7 | |||
| 5f3bd5137f | |||
| 6c9fc5fb86 | |||
| f7e0b68643 | |||
| b283bbaca9 | |||
| 92ba759b1c | |||
| 0acc9d8d68 | |||
| daa7a9ff61 | |||
| 455f35e0c1 | |||
| 1fa655b56e | |||
| e553222b4b | |||
| f1b6f48926 | |||
| 14ab1cae69 | |||
| 5f9cf90b16 | |||
| 97b367d4ee | |||
| 47119fb346 | |||
| d77eb7f5f1 | |||
| 1b0a2bb34c | |||
| a363039fa1 | |||
| 32c740b58e | |||
| 822ee890af | |||
| b0406dd8aa | |||
| 8d152ddfcb | |||
| 1a16d2e4f4 | |||
| 1ca8531305 | |||
| 6190e7d092 | |||
| a6542dd638 | |||
| 840777a851 | |||
| 5c9dff38c9 | |||
| abfbacb8c2 | |||
| 03afdbf431 | |||
| 507d43b328 | |||
| be214c0599 | |||
| 91f36c3a3f | |||
| f60c15ed2e | |||
| 1ec072373d | |||
| a7d039082e | |||
| d5c06bfa58 | |||
| c8f3a0ce7b | |||
| edbedc181b | |||
| 94afa34780 | |||
| 74dd0ab6cd | |||
| 6c43a331d0 | |||
| 67835ba0c0 | |||
| fe1b2a0e07 | |||
| 2e82be47ed | |||
| 15dfd6dcba | |||
| dfd38943b0 | |||
| 500756d582 | |||
| f855f541d8 | |||
| 590ec6643d | |||
| b9efd35b50 | |||
| 3be1bfe58a | |||
| bfbd2de778 | |||
| 50f7ae338a | |||
| 280fdc89c1 | |||
| 0f94e2ad0c | |||
| d0036600e9 | |||
| f766fefbf0 | |||
| 2178ed2a31 | |||
| 79511aea5f | |||
| 19bb77273b | |||
| 7ca377f1b8 | |||
| 6f5e9b7b5a | |||
| f03a58079d | |||
| 8715fe1822 | |||
| e10cb9adaf | |||
| 3030832711 | |||
| 7c638ef28a | |||
| 447a4b5e04 | |||
| dd1ba6e302 | |||
| 08556b751f | |||
| 11d2190310 | |||
| 46519bb710 | |||
| 7b9290382d | |||
| 85e55cf0e4 | |||
| 7016854b7f | |||
| 01a4151763 | |||
| 311f758cd8 | |||
| 3bba36742a | |||
| ea52ba51ca | |||
| 7e404ae71a | |||
| 3ad95afa84 | |||
| 1a13861647 | |||
| 6d84c819c0 | |||
| 36ec39d2fb | |||
| eaafc12c8a | |||
| a03c5bceea | |||
| 53c51fd204 | |||
| 9b593f2a6b | |||
| 363609cb1b | |||
| 68e6d30093 | |||
| c53a40ef8d | |||
| 3c2ac05c60 | |||
| bffab433f6 | |||
| 200b6ce01f | |||
| 0d29dc1012 | |||
| b92c4ae650 | |||
| e15d1e52bd | |||
| ce3e259bde | |||
| 87361f90b1 | |||
| 81be06598c | |||
| 9492da6789 | |||
| fce47a0a37 | |||
| 92ba682198 | |||
| 8df0482eb4 | |||
| 37a5cb347d | |||
| 77e50655f6 | |||
| eacd2abc00 | |||
| 7393954ff6 | |||
| 68744379a6 | |||
| 90aef905af | |||
| c7cbc8966e | |||
| 89502bed63 |
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
name: Revisions Server
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: revisions_server
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*standardnotes/revisions-server*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
call_server_application_workflow:
|
||||||
|
name: Server Application
|
||||||
|
uses: standardnotes/server/.github/workflows/common-server-application.yml@main
|
||||||
|
with:
|
||||||
|
service_name: revisions
|
||||||
|
workspace_name: "@standardnotes/revisions-server"
|
||||||
|
e2e_tag_parameter_name: revisions_image_tag
|
||||||
|
package_path: packages/revisions
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
newrelic:
|
||||||
|
needs: call_server_application_workflow
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Create New Relic deployment marker for Web
|
||||||
|
uses: newrelic/deployment-marker-action@v1
|
||||||
|
with:
|
||||||
|
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
|
||||||
|
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
|
||||||
|
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_REVISIONS_WEB_PROD }}
|
||||||
|
revision: "${{ github.sha }}"
|
||||||
|
description: "Automated Deployment via Github Actions"
|
||||||
|
user: "${{ github.actor }}"
|
||||||
|
- name: Create New Relic deployment marker for Worker
|
||||||
|
uses: newrelic/deployment-marker-action@v1
|
||||||
|
with:
|
||||||
|
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
|
||||||
|
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
|
||||||
|
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_REVISIONS_WORKER_PROD }}
|
||||||
|
revision: "${{ github.sha }}"
|
||||||
|
description: "Automated Deployment via Github Actions"
|
||||||
|
user: "${{ github.actor }}"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 120,
|
||||||
|
"semi": false
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
Vendored
+2
-2
@@ -326,8 +326,8 @@ ifeq ($(strip $(foreach prefix,$(NO_LOAD),\
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
quiet_cmd_regen_makefile = ACTION Regenerating $@
|
quiet_cmd_regen_makefile = ACTION Regenerating $@
|
||||||
cmd_regen_makefile = cd $(srcdir); /Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/karolsojko/Library/Caches/node-gyp/16.15.1" "-Dnode_gyp_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp" "-Dnode_lib_file=/Users/karolsojko/Library/Caches/node-gyp/16.15.1/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics/build/config.gypi -I/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/common.gypi "--toplevel-dir=." binding.gyp
|
cmd_regen_makefile = cd $(srcdir); /Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/karolsojko/Library/Caches/node-gyp/18.12.1" "-Dnode_gyp_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp" "-Dnode_lib_file=/Users/karolsojko/Library/Caches/node-gyp/18.12.1/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics/build/config.gypi -I/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi -I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/include/node/common.gypi "--toplevel-dir=." binding.gyp
|
||||||
Makefile: $(srcdir)/binding.gyp $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi $(srcdir)/../../../../../../../../Library/Caches/node-gyp/16.15.1/include/node/common.gypi $(srcdir)/build/config.gypi
|
Makefile: $(srcdir)/build/config.gypi $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi $(srcdir)/../../../../../../../../Library/Caches/node-gyp/18.12.1/include/node/common.gypi $(srcdir)/binding.gyp
|
||||||
$(call do_cmd,regen_makefile)
|
$(call do_cmd,regen_makefile)
|
||||||
|
|
||||||
# "all" is a concatenation of the "all" targets from all the included
|
# "all" is a concatenation of the "all" targets from all the included
|
||||||
|
|||||||
Generated
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
cmd_Release/native_metrics.node := c++ -bundle -undefined dynamic_lookup -Wl,-search_paths_first -mmacosx-version-min=10.13 -arch x86_64 -L./Release -stdlib=libc++ -o Release/native_metrics.node Release/obj.target/native_metrics/src/native_metrics.o Release/obj.target/native_metrics/src/GCBinder.o Release/obj.target/native_metrics/src/LoopChecker.o
|
|
||||||
Generated
Vendored
-69
@@ -1,69 +0,0 @@
|
|||||||
cmd_Release/obj.target/native_metrics/src/GCBinder.o := c++ -o Release/obj.target/native_metrics/src/GCBinder.o ../src/GCBinder.cpp '-DNODE_GYP_MODULE_NAME=native_metrics' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_GLIBCXX_USE_CXX11_ABI=1' '-D_DARWIN_USE_64_BIT_INODE=1' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DNOMINMAX' '-DBUILDING_NODE_EXTENSION' -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include -I../src -I../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan -O3 -gdwarf-2 -mmacosx-version-min=10.13 -arch x86_64 -Wall -Wendif-labels -W -Wno-unused-parameter -std=gnu++14 -stdlib=libc++ -fno-rtti -fno-exceptions -fno-strict-aliasing -MMD -MF ./Release/.deps/Release/obj.target/native_metrics/src/GCBinder.o.d.raw -c
|
|
||||||
Release/obj.target/native_metrics/src/GCBinder.o: ../src/GCBinder.cpp \
|
|
||||||
../src/GCBinder.hpp \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h \
|
|
||||||
../src/Metric.hpp
|
|
||||||
../src/GCBinder.cpp:
|
|
||||||
../src/GCBinder.hpp:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h:
|
|
||||||
../src/Metric.hpp:
|
|
||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
cmd_Release/obj.target/native_metrics/src/LoopChecker.o := c++ -o Release/obj.target/native_metrics/src/LoopChecker.o ../src/LoopChecker.cpp '-DNODE_GYP_MODULE_NAME=native_metrics' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_GLIBCXX_USE_CXX11_ABI=1' '-D_DARWIN_USE_64_BIT_INODE=1' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DNOMINMAX' '-DBUILDING_NODE_EXTENSION' -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include -I../src -I../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan -O3 -gdwarf-2 -mmacosx-version-min=10.13 -arch x86_64 -Wall -Wendif-labels -W -Wno-unused-parameter -std=gnu++14 -stdlib=libc++ -fno-rtti -fno-exceptions -fno-strict-aliasing -MMD -MF ./Release/.deps/Release/obj.target/native_metrics/src/LoopChecker.o.d.raw -c
|
|
||||||
Release/obj.target/native_metrics/src/LoopChecker.o: \
|
|
||||||
../src/LoopChecker.cpp \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h \
|
|
||||||
../src/LoopChecker.hpp \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h \
|
|
||||||
../src/Metric.hpp
|
|
||||||
../src/LoopChecker.cpp:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h:
|
|
||||||
../src/LoopChecker.hpp:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h:
|
|
||||||
../src/Metric.hpp:
|
|
||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
cmd_Release/obj.target/native_metrics/src/native_metrics.o := c++ -o Release/obj.target/native_metrics/src/native_metrics.o ../src/native_metrics.cpp '-DNODE_GYP_MODULE_NAME=native_metrics' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-DV8_DEPRECATION_WARNINGS' '-DV8_IMMINENT_DEPRECATION_WARNINGS' '-D_GLIBCXX_USE_CXX11_ABI=1' '-D_DARWIN_USE_64_BIT_INODE=1' '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-DOPENSSL_NO_PINSHARED' '-DOPENSSL_THREADS' '-DNOMINMAX' '-DBUILDING_NODE_EXTENSION' -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include -I../src -I../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan -O3 -gdwarf-2 -mmacosx-version-min=10.13 -arch x86_64 -Wall -Wendif-labels -W -Wno-unused-parameter -std=gnu++14 -stdlib=libc++ -fno-rtti -fno-exceptions -fno-strict-aliasing -MMD -MF ./Release/.deps/Release/obj.target/native_metrics/src/native_metrics.o.d.raw -c
|
|
||||||
Release/obj.target/native_metrics/src/native_metrics.o: \
|
|
||||||
../src/native_metrics.cpp \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h \
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h \
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h \
|
|
||||||
../src/GCBinder.hpp ../src/Metric.hpp ../src/LoopChecker.hpp
|
|
||||||
../src/native_metrics.cpp:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/errno.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/unix.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/threadpool.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/uv/darwin.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/cppgc/common.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8config.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-internal.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-version.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/v8-platform.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_buffer.h:
|
|
||||||
/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/node_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_callbacks_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_maybe_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_converters_43_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_new.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_implementation_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_persistent_12_inl.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_weak.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_object_wrap.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_private.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_typedarray_contents.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_json.h:
|
|
||||||
../../../../../nan-npm-2.16.0-cac314a230/node_modules/nan/nan_scriptorigin.h:
|
|
||||||
../src/GCBinder.hpp:
|
|
||||||
../src/Metric.hpp:
|
|
||||||
../src/LoopChecker.hpp:
|
|
||||||
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+271
-246
@@ -19,293 +19,316 @@
|
|||||||
"error_on_warn": "false",
|
"error_on_warn": "false",
|
||||||
"force_dynamic_crt": 0,
|
"force_dynamic_crt": 0,
|
||||||
"host_arch": "x64",
|
"host_arch": "x64",
|
||||||
"icu_data_in": "../../deps/icu-tmp/icudt70l.dat",
|
"icu_data_in": "../../deps/icu-tmp/icudt71l.dat",
|
||||||
"icu_endianness": "l",
|
"icu_endianness": "l",
|
||||||
"icu_gyp_path": "tools/icu/icu-generic.gyp",
|
"icu_gyp_path": "tools/icu/icu-generic.gyp",
|
||||||
"icu_path": "deps/icu-small",
|
"icu_path": "deps/icu-small",
|
||||||
"icu_small": "false",
|
"icu_small": "false",
|
||||||
"icu_ver_major": "70",
|
"icu_ver_major": "71",
|
||||||
"is_debug": 0,
|
"is_debug": 0,
|
||||||
|
"libdir": "lib",
|
||||||
"llvm_version": "11.0",
|
"llvm_version": "11.0",
|
||||||
"napi_build_version": "8",
|
"napi_build_version": "8",
|
||||||
"node_byteorder": "little",
|
"node_byteorder": "little",
|
||||||
"node_debug_lib": "false",
|
"node_debug_lib": "false",
|
||||||
"node_enable_d8": "false",
|
"node_enable_d8": "false",
|
||||||
|
"node_fipsinstall": "false",
|
||||||
"node_install_corepack": "true",
|
"node_install_corepack": "true",
|
||||||
"node_install_npm": "true",
|
"node_install_npm": "true",
|
||||||
"node_library_files": [
|
"node_library_files": [
|
||||||
"lib/constants.js",
|
|
||||||
"lib/net.js",
|
|
||||||
"lib/trace_events.js",
|
|
||||||
"lib/events.js",
|
|
||||||
"lib/repl.js",
|
|
||||||
"lib/util.js",
|
|
||||||
"lib/dgram.js",
|
|
||||||
"lib/vm.js",
|
|
||||||
"lib/stream.js",
|
|
||||||
"lib/child_process.js",
|
|
||||||
"lib/assert.js",
|
|
||||||
"lib/_tls_wrap.js",
|
|
||||||
"lib/http2.js",
|
|
||||||
"lib/inspector.js",
|
|
||||||
"lib/os.js",
|
|
||||||
"lib/_http_server.js",
|
|
||||||
"lib/console.js",
|
|
||||||
"lib/perf_hooks.js",
|
|
||||||
"lib/readline.js",
|
|
||||||
"lib/punycode.js",
|
|
||||||
"lib/_http_incoming.js",
|
|
||||||
"lib/https.js",
|
|
||||||
"lib/_stream_wrap.js",
|
|
||||||
"lib/domain.js",
|
|
||||||
"lib/dns.js",
|
|
||||||
"lib/_http_client.js",
|
|
||||||
"lib/diagnostics_channel.js",
|
|
||||||
"lib/tty.js",
|
|
||||||
"lib/_http_agent.js",
|
"lib/_http_agent.js",
|
||||||
"lib/timers.js",
|
"lib/_http_client.js",
|
||||||
"lib/_http_outgoing.js",
|
|
||||||
"lib/querystring.js",
|
|
||||||
"lib/_tls_common.js",
|
|
||||||
"lib/module.js",
|
|
||||||
"lib/_stream_passthrough.js",
|
|
||||||
"lib/_stream_transform.js",
|
|
||||||
"lib/worker_threads.js",
|
|
||||||
"lib/sys.js",
|
|
||||||
"lib/_stream_duplex.js",
|
|
||||||
"lib/path.js",
|
|
||||||
"lib/_http_common.js",
|
"lib/_http_common.js",
|
||||||
"lib/string_decoder.js",
|
"lib/_http_incoming.js",
|
||||||
"lib/cluster.js",
|
"lib/_http_outgoing.js",
|
||||||
"lib/v8.js",
|
"lib/_http_server.js",
|
||||||
"lib/crypto.js",
|
"lib/_stream_duplex.js",
|
||||||
"lib/wasi.js",
|
"lib/_stream_passthrough.js",
|
||||||
"lib/_stream_readable.js",
|
"lib/_stream_readable.js",
|
||||||
"lib/zlib.js",
|
"lib/_stream_transform.js",
|
||||||
"lib/url.js",
|
"lib/_stream_wrap.js",
|
||||||
"lib/tls.js",
|
|
||||||
"lib/_stream_writable.js",
|
"lib/_stream_writable.js",
|
||||||
|
"lib/_tls_common.js",
|
||||||
|
"lib/_tls_wrap.js",
|
||||||
|
"lib/assert.js",
|
||||||
|
"lib/assert/strict.js",
|
||||||
"lib/async_hooks.js",
|
"lib/async_hooks.js",
|
||||||
"lib/process.js",
|
|
||||||
"lib/http.js",
|
|
||||||
"lib/buffer.js",
|
"lib/buffer.js",
|
||||||
|
"lib/child_process.js",
|
||||||
|
"lib/cluster.js",
|
||||||
|
"lib/console.js",
|
||||||
|
"lib/constants.js",
|
||||||
|
"lib/crypto.js",
|
||||||
|
"lib/dgram.js",
|
||||||
|
"lib/diagnostics_channel.js",
|
||||||
|
"lib/dns.js",
|
||||||
|
"lib/dns/promises.js",
|
||||||
|
"lib/domain.js",
|
||||||
|
"lib/events.js",
|
||||||
"lib/fs.js",
|
"lib/fs.js",
|
||||||
"lib/util/types.js",
|
"lib/fs/promises.js",
|
||||||
"lib/timers/promises.js",
|
"lib/http.js",
|
||||||
"lib/path/win32.js",
|
"lib/http2.js",
|
||||||
"lib/path/posix.js",
|
"lib/https.js",
|
||||||
"lib/stream/consumers.js",
|
"lib/inspector.js",
|
||||||
"lib/stream/promises.js",
|
|
||||||
"lib/stream/web.js",
|
|
||||||
"lib/internal/constants.js",
|
|
||||||
"lib/internal/abort_controller.js",
|
"lib/internal/abort_controller.js",
|
||||||
"lib/internal/net.js",
|
|
||||||
"lib/internal/v8_prof_processor.js",
|
|
||||||
"lib/internal/event_target.js",
|
|
||||||
"lib/internal/inspector_async_hook.js",
|
|
||||||
"lib/internal/validators.js",
|
|
||||||
"lib/internal/linkedlist.js",
|
|
||||||
"lib/internal/cli_table.js",
|
|
||||||
"lib/internal/repl.js",
|
|
||||||
"lib/internal/util.js",
|
|
||||||
"lib/internal/histogram.js",
|
|
||||||
"lib/internal/error_serdes.js",
|
|
||||||
"lib/internal/dgram.js",
|
|
||||||
"lib/internal/child_process.js",
|
|
||||||
"lib/internal/assert.js",
|
"lib/internal/assert.js",
|
||||||
"lib/internal/fixed_queue.js",
|
"lib/internal/assert/assertion_error.js",
|
||||||
"lib/internal/blocklist.js",
|
"lib/internal/assert/calltracker.js",
|
||||||
"lib/internal/v8_prof_polyfill.js",
|
"lib/internal/assert/snapshot.js",
|
||||||
"lib/internal/options.js",
|
|
||||||
"lib/internal/worker.js",
|
|
||||||
"lib/internal/dtrace.js",
|
|
||||||
"lib/internal/idna.js",
|
|
||||||
"lib/internal/watchdog.js",
|
|
||||||
"lib/internal/encoding.js",
|
|
||||||
"lib/internal/tty.js",
|
|
||||||
"lib/internal/freeze_intrinsics.js",
|
|
||||||
"lib/internal/timers.js",
|
|
||||||
"lib/internal/heap_utils.js",
|
|
||||||
"lib/internal/querystring.js",
|
|
||||||
"lib/internal/js_stream_socket.js",
|
|
||||||
"lib/internal/errors.js",
|
|
||||||
"lib/internal/priority_queue.js",
|
|
||||||
"lib/internal/freelist.js",
|
|
||||||
"lib/internal/blob.js",
|
|
||||||
"lib/internal/socket_list.js",
|
|
||||||
"lib/internal/socketaddress.js",
|
|
||||||
"lib/internal/promise_hooks.js",
|
|
||||||
"lib/internal/stream_base_commons.js",
|
|
||||||
"lib/internal/url.js",
|
|
||||||
"lib/internal/async_hooks.js",
|
"lib/internal/async_hooks.js",
|
||||||
"lib/internal/http.js",
|
"lib/internal/blob.js",
|
||||||
"lib/internal/buffer.js",
|
"lib/internal/blocklist.js",
|
||||||
"lib/internal/trace_events_async_hooks.js",
|
"lib/internal/bootstrap/browser.js",
|
||||||
"lib/internal/crypto/sig.js",
|
|
||||||
"lib/internal/crypto/rsa.js",
|
|
||||||
"lib/internal/crypto/aes.js",
|
|
||||||
"lib/internal/crypto/util.js",
|
|
||||||
"lib/internal/crypto/scrypt.js",
|
|
||||||
"lib/internal/crypto/random.js",
|
|
||||||
"lib/internal/crypto/keys.js",
|
|
||||||
"lib/internal/crypto/x509.js",
|
|
||||||
"lib/internal/crypto/certificate.js",
|
|
||||||
"lib/internal/crypto/ec.js",
|
|
||||||
"lib/internal/crypto/keygen.js",
|
|
||||||
"lib/internal/crypto/mac.js",
|
|
||||||
"lib/internal/crypto/diffiehellman.js",
|
|
||||||
"lib/internal/crypto/hkdf.js",
|
|
||||||
"lib/internal/crypto/cipher.js",
|
|
||||||
"lib/internal/crypto/hash.js",
|
|
||||||
"lib/internal/crypto/pbkdf2.js",
|
|
||||||
"lib/internal/crypto/webcrypto.js",
|
|
||||||
"lib/internal/crypto/dsa.js",
|
|
||||||
"lib/internal/crypto/hashnames.js",
|
|
||||||
"lib/internal/cluster/shared_handle.js",
|
|
||||||
"lib/internal/cluster/round_robin_handle.js",
|
|
||||||
"lib/internal/cluster/worker.js",
|
|
||||||
"lib/internal/cluster/primary.js",
|
|
||||||
"lib/internal/cluster/utils.js",
|
|
||||||
"lib/internal/cluster/child.js",
|
|
||||||
"lib/internal/webstreams/compression.js",
|
|
||||||
"lib/internal/webstreams/util.js",
|
|
||||||
"lib/internal/webstreams/writablestream.js",
|
|
||||||
"lib/internal/webstreams/readablestream.js",
|
|
||||||
"lib/internal/webstreams/queuingstrategies.js",
|
|
||||||
"lib/internal/webstreams/encoding.js",
|
|
||||||
"lib/internal/webstreams/transformstream.js",
|
|
||||||
"lib/internal/webstreams/adapters.js",
|
|
||||||
"lib/internal/webstreams/transfer.js",
|
|
||||||
"lib/internal/bootstrap/loaders.js",
|
"lib/internal/bootstrap/loaders.js",
|
||||||
"lib/internal/bootstrap/pre_execution.js",
|
|
||||||
"lib/internal/bootstrap/node.js",
|
"lib/internal/bootstrap/node.js",
|
||||||
"lib/internal/bootstrap/environment.js",
|
|
||||||
"lib/internal/bootstrap/switches/does_not_own_process_state.js",
|
"lib/internal/bootstrap/switches/does_not_own_process_state.js",
|
||||||
"lib/internal/bootstrap/switches/is_not_main_thread.js",
|
|
||||||
"lib/internal/bootstrap/switches/does_own_process_state.js",
|
"lib/internal/bootstrap/switches/does_own_process_state.js",
|
||||||
"lib/internal/bootstrap/switches/is_main_thread.js",
|
"lib/internal/bootstrap/switches/is_main_thread.js",
|
||||||
"lib/internal/test/binding.js",
|
"lib/internal/bootstrap/switches/is_not_main_thread.js",
|
||||||
"lib/internal/test/transfer.js",
|
"lib/internal/buffer.js",
|
||||||
"lib/internal/util/types.js",
|
"lib/internal/child_process.js",
|
||||||
"lib/internal/util/inspector.js",
|
|
||||||
"lib/internal/util/comparisons.js",
|
|
||||||
"lib/internal/util/debuglog.js",
|
|
||||||
"lib/internal/util/inspect.js",
|
|
||||||
"lib/internal/util/iterable_weak_map.js",
|
|
||||||
"lib/internal/streams/add-abort-signal.js",
|
|
||||||
"lib/internal/streams/compose.js",
|
|
||||||
"lib/internal/streams/duplexify.js",
|
|
||||||
"lib/internal/streams/destroy.js",
|
|
||||||
"lib/internal/streams/legacy.js",
|
|
||||||
"lib/internal/streams/passthrough.js",
|
|
||||||
"lib/internal/streams/operators.js",
|
|
||||||
"lib/internal/streams/readable.js",
|
|
||||||
"lib/internal/streams/from.js",
|
|
||||||
"lib/internal/streams/writable.js",
|
|
||||||
"lib/internal/streams/state.js",
|
|
||||||
"lib/internal/streams/buffer_list.js",
|
|
||||||
"lib/internal/streams/end-of-stream.js",
|
|
||||||
"lib/internal/streams/utils.js",
|
|
||||||
"lib/internal/streams/transform.js",
|
|
||||||
"lib/internal/streams/lazy_transform.js",
|
|
||||||
"lib/internal/streams/duplex.js",
|
|
||||||
"lib/internal/streams/pipeline.js",
|
|
||||||
"lib/internal/readline/interface.js",
|
|
||||||
"lib/internal/readline/utils.js",
|
|
||||||
"lib/internal/readline/emitKeypressEvents.js",
|
|
||||||
"lib/internal/readline/callbacks.js",
|
|
||||||
"lib/internal/repl/history.js",
|
|
||||||
"lib/internal/repl/utils.js",
|
|
||||||
"lib/internal/repl/await.js",
|
|
||||||
"lib/internal/legacy/processbinding.js",
|
|
||||||
"lib/internal/assert/calltracker.js",
|
|
||||||
"lib/internal/assert/assertion_error.js",
|
|
||||||
"lib/internal/http2/util.js",
|
|
||||||
"lib/internal/http2/core.js",
|
|
||||||
"lib/internal/http2/compat.js",
|
|
||||||
"lib/internal/per_context/messageport.js",
|
|
||||||
"lib/internal/per_context/primordials.js",
|
|
||||||
"lib/internal/per_context/domexception.js",
|
|
||||||
"lib/internal/vm/module.js",
|
|
||||||
"lib/internal/tls/secure-pair.js",
|
|
||||||
"lib/internal/tls/parse-cert-string.js",
|
|
||||||
"lib/internal/tls/secure-context.js",
|
|
||||||
"lib/internal/child_process/serialization.js",
|
"lib/internal/child_process/serialization.js",
|
||||||
"lib/internal/debugger/inspect_repl.js",
|
"lib/internal/cli_table.js",
|
||||||
"lib/internal/debugger/inspect_client.js",
|
"lib/internal/cluster/child.js",
|
||||||
|
"lib/internal/cluster/primary.js",
|
||||||
|
"lib/internal/cluster/round_robin_handle.js",
|
||||||
|
"lib/internal/cluster/shared_handle.js",
|
||||||
|
"lib/internal/cluster/utils.js",
|
||||||
|
"lib/internal/cluster/worker.js",
|
||||||
|
"lib/internal/console/constructor.js",
|
||||||
|
"lib/internal/console/global.js",
|
||||||
|
"lib/internal/constants.js",
|
||||||
|
"lib/internal/crypto/aes.js",
|
||||||
|
"lib/internal/crypto/certificate.js",
|
||||||
|
"lib/internal/crypto/cfrg.js",
|
||||||
|
"lib/internal/crypto/cipher.js",
|
||||||
|
"lib/internal/crypto/diffiehellman.js",
|
||||||
|
"lib/internal/crypto/ec.js",
|
||||||
|
"lib/internal/crypto/hash.js",
|
||||||
|
"lib/internal/crypto/hashnames.js",
|
||||||
|
"lib/internal/crypto/hkdf.js",
|
||||||
|
"lib/internal/crypto/keygen.js",
|
||||||
|
"lib/internal/crypto/keys.js",
|
||||||
|
"lib/internal/crypto/mac.js",
|
||||||
|
"lib/internal/crypto/pbkdf2.js",
|
||||||
|
"lib/internal/crypto/random.js",
|
||||||
|
"lib/internal/crypto/rsa.js",
|
||||||
|
"lib/internal/crypto/scrypt.js",
|
||||||
|
"lib/internal/crypto/sig.js",
|
||||||
|
"lib/internal/crypto/util.js",
|
||||||
|
"lib/internal/crypto/webcrypto.js",
|
||||||
|
"lib/internal/crypto/x509.js",
|
||||||
"lib/internal/debugger/inspect.js",
|
"lib/internal/debugger/inspect.js",
|
||||||
"lib/internal/worker/io.js",
|
"lib/internal/debugger/inspect_client.js",
|
||||||
"lib/internal/worker/js_transferable.js",
|
"lib/internal/debugger/inspect_repl.js",
|
||||||
"lib/internal/main/repl.js",
|
"lib/internal/dgram.js",
|
||||||
"lib/internal/main/print_help.js",
|
"lib/internal/dns/callback_resolver.js",
|
||||||
"lib/internal/main/eval_string.js",
|
|
||||||
"lib/internal/main/check_syntax.js",
|
|
||||||
"lib/internal/main/prof_process.js",
|
|
||||||
"lib/internal/main/worker_thread.js",
|
|
||||||
"lib/internal/main/inspect.js",
|
|
||||||
"lib/internal/main/eval_stdin.js",
|
|
||||||
"lib/internal/main/run_main_module.js",
|
|
||||||
"lib/internal/modules/run_main.js",
|
|
||||||
"lib/internal/modules/package_json_reader.js",
|
|
||||||
"lib/internal/modules/esm/module_job.js",
|
|
||||||
"lib/internal/modules/esm/assert.js",
|
|
||||||
"lib/internal/modules/esm/fetch_module.js",
|
|
||||||
"lib/internal/modules/esm/get_source.js",
|
|
||||||
"lib/internal/modules/esm/translators.js",
|
|
||||||
"lib/internal/modules/esm/resolve.js",
|
|
||||||
"lib/internal/modules/esm/create_dynamic_module.js",
|
|
||||||
"lib/internal/modules/esm/load.js",
|
|
||||||
"lib/internal/modules/esm/handle_process_exit.js",
|
|
||||||
"lib/internal/modules/esm/initialize_import_meta.js",
|
|
||||||
"lib/internal/modules/esm/module_map.js",
|
|
||||||
"lib/internal/modules/esm/get_format.js",
|
|
||||||
"lib/internal/modules/esm/formats.js",
|
|
||||||
"lib/internal/modules/esm/loader.js",
|
|
||||||
"lib/internal/modules/cjs/helpers.js",
|
|
||||||
"lib/internal/modules/cjs/loader.js",
|
|
||||||
"lib/internal/source_map/source_map.js",
|
|
||||||
"lib/internal/source_map/prepare_stack_trace.js",
|
|
||||||
"lib/internal/source_map/source_map_cache.js",
|
|
||||||
"lib/internal/dns/promises.js",
|
"lib/internal/dns/promises.js",
|
||||||
"lib/internal/dns/utils.js",
|
"lib/internal/dns/utils.js",
|
||||||
"lib/internal/fs/watchers.js",
|
"lib/internal/dtrace.js",
|
||||||
|
"lib/internal/encoding.js",
|
||||||
|
"lib/internal/error_serdes.js",
|
||||||
|
"lib/internal/errors.js",
|
||||||
|
"lib/internal/event_target.js",
|
||||||
|
"lib/internal/fixed_queue.js",
|
||||||
|
"lib/internal/freelist.js",
|
||||||
|
"lib/internal/freeze_intrinsics.js",
|
||||||
|
"lib/internal/fs/cp/cp-sync.js",
|
||||||
|
"lib/internal/fs/cp/cp.js",
|
||||||
|
"lib/internal/fs/dir.js",
|
||||||
"lib/internal/fs/promises.js",
|
"lib/internal/fs/promises.js",
|
||||||
"lib/internal/fs/read_file_context.js",
|
"lib/internal/fs/read_file_context.js",
|
||||||
"lib/internal/fs/rimraf.js",
|
"lib/internal/fs/rimraf.js",
|
||||||
"lib/internal/fs/sync_write_stream.js",
|
|
||||||
"lib/internal/fs/dir.js",
|
|
||||||
"lib/internal/fs/streams.js",
|
"lib/internal/fs/streams.js",
|
||||||
|
"lib/internal/fs/sync_write_stream.js",
|
||||||
"lib/internal/fs/utils.js",
|
"lib/internal/fs/utils.js",
|
||||||
"lib/internal/fs/cp/cp.js",
|
"lib/internal/fs/watchers.js",
|
||||||
"lib/internal/fs/cp/cp-sync.js",
|
"lib/internal/heap_utils.js",
|
||||||
"lib/internal/perf/nodetiming.js",
|
"lib/internal/histogram.js",
|
||||||
"lib/internal/perf/usertiming.js",
|
"lib/internal/http.js",
|
||||||
"lib/internal/perf/performance_entry.js",
|
"lib/internal/http2/compat.js",
|
||||||
"lib/internal/perf/performance.js",
|
"lib/internal/http2/core.js",
|
||||||
"lib/internal/perf/timerify.js",
|
"lib/internal/http2/util.js",
|
||||||
"lib/internal/perf/utils.js",
|
"lib/internal/idna.js",
|
||||||
"lib/internal/perf/observe.js",
|
"lib/internal/inspector_async_hook.js",
|
||||||
|
"lib/internal/js_stream_socket.js",
|
||||||
|
"lib/internal/legacy/processbinding.js",
|
||||||
|
"lib/internal/linkedlist.js",
|
||||||
|
"lib/internal/main/check_syntax.js",
|
||||||
|
"lib/internal/main/environment.js",
|
||||||
|
"lib/internal/main/eval_stdin.js",
|
||||||
|
"lib/internal/main/eval_string.js",
|
||||||
|
"lib/internal/main/inspect.js",
|
||||||
|
"lib/internal/main/mksnapshot.js",
|
||||||
|
"lib/internal/main/print_help.js",
|
||||||
|
"lib/internal/main/prof_process.js",
|
||||||
|
"lib/internal/main/repl.js",
|
||||||
|
"lib/internal/main/run_main_module.js",
|
||||||
|
"lib/internal/main/test_runner.js",
|
||||||
|
"lib/internal/main/watch_mode.js",
|
||||||
|
"lib/internal/main/worker_thread.js",
|
||||||
|
"lib/internal/modules/cjs/helpers.js",
|
||||||
|
"lib/internal/modules/cjs/loader.js",
|
||||||
|
"lib/internal/modules/esm/assert.js",
|
||||||
|
"lib/internal/modules/esm/create_dynamic_module.js",
|
||||||
|
"lib/internal/modules/esm/fetch_module.js",
|
||||||
|
"lib/internal/modules/esm/formats.js",
|
||||||
|
"lib/internal/modules/esm/get_format.js",
|
||||||
|
"lib/internal/modules/esm/handle_process_exit.js",
|
||||||
|
"lib/internal/modules/esm/initialize_import_meta.js",
|
||||||
|
"lib/internal/modules/esm/load.js",
|
||||||
|
"lib/internal/modules/esm/loader.js",
|
||||||
|
"lib/internal/modules/esm/module_job.js",
|
||||||
|
"lib/internal/modules/esm/module_map.js",
|
||||||
|
"lib/internal/modules/esm/package_config.js",
|
||||||
|
"lib/internal/modules/esm/resolve.js",
|
||||||
|
"lib/internal/modules/esm/translators.js",
|
||||||
|
"lib/internal/modules/package_json_reader.js",
|
||||||
|
"lib/internal/modules/run_main.js",
|
||||||
|
"lib/internal/net.js",
|
||||||
|
"lib/internal/options.js",
|
||||||
|
"lib/internal/per_context/domexception.js",
|
||||||
|
"lib/internal/per_context/messageport.js",
|
||||||
|
"lib/internal/per_context/primordials.js",
|
||||||
"lib/internal/perf/event_loop_delay.js",
|
"lib/internal/perf/event_loop_delay.js",
|
||||||
"lib/internal/perf/event_loop_utilization.js",
|
"lib/internal/perf/event_loop_utilization.js",
|
||||||
|
"lib/internal/perf/nodetiming.js",
|
||||||
|
"lib/internal/perf/observe.js",
|
||||||
|
"lib/internal/perf/performance.js",
|
||||||
|
"lib/internal/perf/performance_entry.js",
|
||||||
|
"lib/internal/perf/resource_timing.js",
|
||||||
|
"lib/internal/perf/timerify.js",
|
||||||
|
"lib/internal/perf/usertiming.js",
|
||||||
|
"lib/internal/perf/utils.js",
|
||||||
"lib/internal/policy/manifest.js",
|
"lib/internal/policy/manifest.js",
|
||||||
"lib/internal/policy/sri.js",
|
"lib/internal/policy/sri.js",
|
||||||
"lib/internal/process/task_queues.js",
|
"lib/internal/priority_queue.js",
|
||||||
"lib/internal/process/per_thread.js",
|
|
||||||
"lib/internal/process/warning.js",
|
|
||||||
"lib/internal/process/policy.js",
|
|
||||||
"lib/internal/process/promises.js",
|
|
||||||
"lib/internal/process/signal.js",
|
|
||||||
"lib/internal/process/execution.js",
|
|
||||||
"lib/internal/process/esm_loader.js",
|
"lib/internal/process/esm_loader.js",
|
||||||
|
"lib/internal/process/execution.js",
|
||||||
|
"lib/internal/process/per_thread.js",
|
||||||
|
"lib/internal/process/policy.js",
|
||||||
|
"lib/internal/process/pre_execution.js",
|
||||||
|
"lib/internal/process/promises.js",
|
||||||
"lib/internal/process/report.js",
|
"lib/internal/process/report.js",
|
||||||
|
"lib/internal/process/signal.js",
|
||||||
|
"lib/internal/process/task_queues.js",
|
||||||
|
"lib/internal/process/warning.js",
|
||||||
"lib/internal/process/worker_thread_only.js",
|
"lib/internal/process/worker_thread_only.js",
|
||||||
"lib/internal/console/constructor.js",
|
"lib/internal/promise_hooks.js",
|
||||||
"lib/internal/console/global.js",
|
"lib/internal/querystring.js",
|
||||||
"lib/assert/strict.js",
|
"lib/internal/readline/callbacks.js",
|
||||||
"lib/dns/promises.js",
|
"lib/internal/readline/emitKeypressEvents.js",
|
||||||
"lib/fs/promises.js"
|
"lib/internal/readline/interface.js",
|
||||||
|
"lib/internal/readline/promises.js",
|
||||||
|
"lib/internal/readline/utils.js",
|
||||||
|
"lib/internal/repl.js",
|
||||||
|
"lib/internal/repl/await.js",
|
||||||
|
"lib/internal/repl/history.js",
|
||||||
|
"lib/internal/repl/utils.js",
|
||||||
|
"lib/internal/socket_list.js",
|
||||||
|
"lib/internal/socketaddress.js",
|
||||||
|
"lib/internal/source_map/prepare_stack_trace.js",
|
||||||
|
"lib/internal/source_map/source_map.js",
|
||||||
|
"lib/internal/source_map/source_map_cache.js",
|
||||||
|
"lib/internal/stream_base_commons.js",
|
||||||
|
"lib/internal/streams/add-abort-signal.js",
|
||||||
|
"lib/internal/streams/buffer_list.js",
|
||||||
|
"lib/internal/streams/compose.js",
|
||||||
|
"lib/internal/streams/destroy.js",
|
||||||
|
"lib/internal/streams/duplex.js",
|
||||||
|
"lib/internal/streams/duplexify.js",
|
||||||
|
"lib/internal/streams/end-of-stream.js",
|
||||||
|
"lib/internal/streams/from.js",
|
||||||
|
"lib/internal/streams/lazy_transform.js",
|
||||||
|
"lib/internal/streams/legacy.js",
|
||||||
|
"lib/internal/streams/operators.js",
|
||||||
|
"lib/internal/streams/passthrough.js",
|
||||||
|
"lib/internal/streams/pipeline.js",
|
||||||
|
"lib/internal/streams/readable.js",
|
||||||
|
"lib/internal/streams/state.js",
|
||||||
|
"lib/internal/streams/transform.js",
|
||||||
|
"lib/internal/streams/utils.js",
|
||||||
|
"lib/internal/streams/writable.js",
|
||||||
|
"lib/internal/structured_clone.js",
|
||||||
|
"lib/internal/test/binding.js",
|
||||||
|
"lib/internal/test/transfer.js",
|
||||||
|
"lib/internal/test_runner/harness.js",
|
||||||
|
"lib/internal/test_runner/runner.js",
|
||||||
|
"lib/internal/test_runner/tap_stream.js",
|
||||||
|
"lib/internal/test_runner/test.js",
|
||||||
|
"lib/internal/test_runner/utils.js",
|
||||||
|
"lib/internal/timers.js",
|
||||||
|
"lib/internal/tls/secure-context.js",
|
||||||
|
"lib/internal/tls/secure-pair.js",
|
||||||
|
"lib/internal/trace_events_async_hooks.js",
|
||||||
|
"lib/internal/tty.js",
|
||||||
|
"lib/internal/url.js",
|
||||||
|
"lib/internal/util.js",
|
||||||
|
"lib/internal/util/colors.js",
|
||||||
|
"lib/internal/util/comparisons.js",
|
||||||
|
"lib/internal/util/debuglog.js",
|
||||||
|
"lib/internal/util/inspect.js",
|
||||||
|
"lib/internal/util/inspector.js",
|
||||||
|
"lib/internal/util/iterable_weak_map.js",
|
||||||
|
"lib/internal/util/parse_args/parse_args.js",
|
||||||
|
"lib/internal/util/parse_args/utils.js",
|
||||||
|
"lib/internal/util/types.js",
|
||||||
|
"lib/internal/v8/startup_snapshot.js",
|
||||||
|
"lib/internal/v8_prof_polyfill.js",
|
||||||
|
"lib/internal/v8_prof_processor.js",
|
||||||
|
"lib/internal/validators.js",
|
||||||
|
"lib/internal/vm/module.js",
|
||||||
|
"lib/internal/wasm_web_api.js",
|
||||||
|
"lib/internal/watch_mode/files_watcher.js",
|
||||||
|
"lib/internal/watchdog.js",
|
||||||
|
"lib/internal/webstreams/adapters.js",
|
||||||
|
"lib/internal/webstreams/compression.js",
|
||||||
|
"lib/internal/webstreams/encoding.js",
|
||||||
|
"lib/internal/webstreams/queuingstrategies.js",
|
||||||
|
"lib/internal/webstreams/readablestream.js",
|
||||||
|
"lib/internal/webstreams/transfer.js",
|
||||||
|
"lib/internal/webstreams/transformstream.js",
|
||||||
|
"lib/internal/webstreams/util.js",
|
||||||
|
"lib/internal/webstreams/writablestream.js",
|
||||||
|
"lib/internal/worker.js",
|
||||||
|
"lib/internal/worker/io.js",
|
||||||
|
"lib/internal/worker/js_transferable.js",
|
||||||
|
"lib/module.js",
|
||||||
|
"lib/net.js",
|
||||||
|
"lib/os.js",
|
||||||
|
"lib/path.js",
|
||||||
|
"lib/path/posix.js",
|
||||||
|
"lib/path/win32.js",
|
||||||
|
"lib/perf_hooks.js",
|
||||||
|
"lib/process.js",
|
||||||
|
"lib/punycode.js",
|
||||||
|
"lib/querystring.js",
|
||||||
|
"lib/readline.js",
|
||||||
|
"lib/readline/promises.js",
|
||||||
|
"lib/repl.js",
|
||||||
|
"lib/stream.js",
|
||||||
|
"lib/stream/consumers.js",
|
||||||
|
"lib/stream/promises.js",
|
||||||
|
"lib/stream/web.js",
|
||||||
|
"lib/string_decoder.js",
|
||||||
|
"lib/sys.js",
|
||||||
|
"lib/test.js",
|
||||||
|
"lib/timers.js",
|
||||||
|
"lib/timers/promises.js",
|
||||||
|
"lib/tls.js",
|
||||||
|
"lib/trace_events.js",
|
||||||
|
"lib/tty.js",
|
||||||
|
"lib/url.js",
|
||||||
|
"lib/util.js",
|
||||||
|
"lib/util/types.js",
|
||||||
|
"lib/v8.js",
|
||||||
|
"lib/vm.js",
|
||||||
|
"lib/wasi.js",
|
||||||
|
"lib/worker_threads.js",
|
||||||
|
"lib/zlib.js"
|
||||||
],
|
],
|
||||||
"node_module_version": 93,
|
"node_module_version": 108,
|
||||||
"node_no_browser_globals": "false",
|
"node_no_browser_globals": "false",
|
||||||
"node_prefix": "/",
|
"node_prefix": "/",
|
||||||
"node_release_urlbase": "https://nodejs.org/download/release/",
|
"node_release_urlbase": "https://nodejs.org/download/release/",
|
||||||
@@ -330,20 +353,22 @@
|
|||||||
"node_use_v8_platform": "true",
|
"node_use_v8_platform": "true",
|
||||||
"node_with_ltcg": "false",
|
"node_with_ltcg": "false",
|
||||||
"node_without_node_options": "false",
|
"node_without_node_options": "false",
|
||||||
"openssl_fips": "",
|
|
||||||
"openssl_is_fips": "false",
|
"openssl_is_fips": "false",
|
||||||
"openssl_quic": "true",
|
"openssl_quic": "true",
|
||||||
"ossfuzz": "false",
|
"ossfuzz": "false",
|
||||||
"shlib_suffix": "93.dylib",
|
"shlib_suffix": "108.dylib",
|
||||||
"target_arch": "x64",
|
"target_arch": "x64",
|
||||||
"v8_enable_31bit_smis_on_64bit_arch": 0,
|
"v8_enable_31bit_smis_on_64bit_arch": 0,
|
||||||
"v8_enable_gdbjit": 0,
|
"v8_enable_gdbjit": 0,
|
||||||
"v8_enable_hugepage": 0,
|
"v8_enable_hugepage": 0,
|
||||||
"v8_enable_i18n_support": 1,
|
"v8_enable_i18n_support": 1,
|
||||||
"v8_enable_inspector": 1,
|
"v8_enable_inspector": 1,
|
||||||
|
"v8_enable_javascript_promise_hooks": 1,
|
||||||
"v8_enable_lite_mode": 0,
|
"v8_enable_lite_mode": 0,
|
||||||
"v8_enable_object_print": 1,
|
"v8_enable_object_print": 1,
|
||||||
"v8_enable_pointer_compression": 0,
|
"v8_enable_pointer_compression": 0,
|
||||||
|
"v8_enable_shared_ro_heap": 1,
|
||||||
|
"v8_enable_short_builtin_calls": 1,
|
||||||
"v8_enable_webassembly": 1,
|
"v8_enable_webassembly": 1,
|
||||||
"v8_no_strict_aliasing": 1,
|
"v8_no_strict_aliasing": 1,
|
||||||
"v8_optimized_debug": 1,
|
"v8_optimized_debug": 1,
|
||||||
@@ -353,8 +378,8 @@
|
|||||||
"v8_use_siphash": 1,
|
"v8_use_siphash": 1,
|
||||||
"want_separate_host_toolset": 0,
|
"want_separate_host_toolset": 0,
|
||||||
"xcode_version": "11.0",
|
"xcode_version": "11.0",
|
||||||
"nodedir": "/Users/karolsojko/Library/Caches/node-gyp/16.15.1",
|
"nodedir": "/Users/karolsojko/Library/Caches/node-gyp/18.12.1",
|
||||||
"standalone_static_library": 1,
|
"standalone_static_library": 1,
|
||||||
"user_agent": "yarn/3.2.1 npm/? node/v16.15.1 darwin x64"
|
"user_agent": "yarn/4.0.0-rc.25 npm/? node/v18.12.1 darwin x64"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
Vendored
+20
-20
@@ -25,7 +25,7 @@ DEFS_Debug := \
|
|||||||
CFLAGS_Debug := \
|
CFLAGS_Debug := \
|
||||||
-O0 \
|
-O0 \
|
||||||
-gdwarf-2 \
|
-gdwarf-2 \
|
||||||
-mmacosx-version-min=10.13 \
|
-mmacosx-version-min=10.15 \
|
||||||
-arch x86_64 \
|
-arch x86_64 \
|
||||||
-Wall \
|
-Wall \
|
||||||
-Wendif-labels \
|
-Wendif-labels \
|
||||||
@@ -38,7 +38,7 @@ CFLAGS_C_Debug := \
|
|||||||
|
|
||||||
# Flags passed to only C++ files.
|
# Flags passed to only C++ files.
|
||||||
CFLAGS_CC_Debug := \
|
CFLAGS_CC_Debug := \
|
||||||
-std=gnu++14 \
|
-std=gnu++17 \
|
||||||
-stdlib=libc++ \
|
-stdlib=libc++ \
|
||||||
-fno-rtti \
|
-fno-rtti \
|
||||||
-fno-exceptions \
|
-fno-exceptions \
|
||||||
@@ -51,13 +51,13 @@ CFLAGS_OBJC_Debug :=
|
|||||||
CFLAGS_OBJCC_Debug :=
|
CFLAGS_OBJCC_Debug :=
|
||||||
|
|
||||||
INCS_Debug := \
|
INCS_Debug := \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/include/node \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/src \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/config \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/openssl/include \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/uv/include \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/zlib \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/v8/include \
|
||||||
-I$(srcdir)/src \
|
-I$(srcdir)/src \
|
||||||
-I$(srcdir)/../../../../nan-npm-2.16.0-cac314a230/node_modules/nan
|
-I$(srcdir)/../../../../nan-npm-2.16.0-cac314a230/node_modules/nan
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ DEFS_Release := \
|
|||||||
CFLAGS_Release := \
|
CFLAGS_Release := \
|
||||||
-O3 \
|
-O3 \
|
||||||
-gdwarf-2 \
|
-gdwarf-2 \
|
||||||
-mmacosx-version-min=10.13 \
|
-mmacosx-version-min=10.15 \
|
||||||
-arch x86_64 \
|
-arch x86_64 \
|
||||||
-Wall \
|
-Wall \
|
||||||
-Wendif-labels \
|
-Wendif-labels \
|
||||||
@@ -94,7 +94,7 @@ CFLAGS_C_Release := \
|
|||||||
|
|
||||||
# Flags passed to only C++ files.
|
# Flags passed to only C++ files.
|
||||||
CFLAGS_CC_Release := \
|
CFLAGS_CC_Release := \
|
||||||
-std=gnu++14 \
|
-std=gnu++17 \
|
||||||
-stdlib=libc++ \
|
-stdlib=libc++ \
|
||||||
-fno-rtti \
|
-fno-rtti \
|
||||||
-fno-exceptions \
|
-fno-exceptions \
|
||||||
@@ -107,13 +107,13 @@ CFLAGS_OBJC_Release :=
|
|||||||
CFLAGS_OBJCC_Release :=
|
CFLAGS_OBJCC_Release :=
|
||||||
|
|
||||||
INCS_Release := \
|
INCS_Release := \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/include/node \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/src \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/src \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/config \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/config \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/openssl/openssl/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/openssl/openssl/include \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/uv/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/uv/include \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/zlib \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/zlib \
|
||||||
-I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/deps/v8/include \
|
-I/Users/karolsojko/Library/Caches/node-gyp/18.12.1/deps/v8/include \
|
||||||
-I$(srcdir)/src \
|
-I$(srcdir)/src \
|
||||||
-I$(srcdir)/../../../../nan-npm-2.16.0-cac314a230/node_modules/nan
|
-I$(srcdir)/../../../../nan-npm-2.16.0-cac314a230/node_modules/nan
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ $(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cpp FORCE_DO_CMD
|
|||||||
LDFLAGS_Debug := \
|
LDFLAGS_Debug := \
|
||||||
-undefined dynamic_lookup \
|
-undefined dynamic_lookup \
|
||||||
-Wl,-search_paths_first \
|
-Wl,-search_paths_first \
|
||||||
-mmacosx-version-min=10.13 \
|
-mmacosx-version-min=10.15 \
|
||||||
-arch x86_64 \
|
-arch x86_64 \
|
||||||
-L$(builddir) \
|
-L$(builddir) \
|
||||||
-stdlib=libc++
|
-stdlib=libc++
|
||||||
@@ -163,7 +163,7 @@ LIBTOOLFLAGS_Debug := \
|
|||||||
LDFLAGS_Release := \
|
LDFLAGS_Release := \
|
||||||
-undefined dynamic_lookup \
|
-undefined dynamic_lookup \
|
||||||
-Wl,-search_paths_first \
|
-Wl,-search_paths_first \
|
||||||
-mmacosx-version-min=10.13 \
|
-mmacosx-version-min=10.15 \
|
||||||
-arch x86_64 \
|
-arch x86_64 \
|
||||||
-L$(builddir) \
|
-L$(builddir) \
|
||||||
-stdlib=libc++
|
-stdlib=libc++
|
||||||
|
|||||||
+7
-5
@@ -8,7 +8,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "yarn workspaces foreach -p -j 10 --verbose run lint",
|
"lint": "yarn workspaces foreach -p -j 10 --verbose run lint",
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"lint:websockets": "yarn workspace @standardnotes/websockets-server lint",
|
"lint:websockets": "yarn workspace @standardnotes/websockets-server lint",
|
||||||
"lint:workspace": "yarn workspace @standardnotes/workspace-server lint",
|
"lint:workspace": "yarn workspace @standardnotes/workspace-server lint",
|
||||||
"lint:analytics": "yarn workspace @standardnotes/analytics lint",
|
"lint:analytics": "yarn workspace @standardnotes/analytics lint",
|
||||||
|
"lint:revisions": "yarn workspace @standardnotes/revisions-server lint",
|
||||||
"clean": "yarn workspaces foreach -p --verbose run clean",
|
"clean": "yarn workspaces foreach -p --verbose run clean",
|
||||||
"setup:env": "cp .env.sample .env && yarn workspaces foreach -p --verbose run setup:env",
|
"setup:env": "cp .env.sample .env && yarn workspaces foreach -p --verbose run setup:env",
|
||||||
"start:auth": "yarn workspace @standardnotes/auth-server start",
|
"start:auth": "yarn workspace @standardnotes/auth-server start",
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
"start:websockets": "yarn workspace @standardnotes/websockets-server start",
|
"start:websockets": "yarn workspace @standardnotes/websockets-server start",
|
||||||
"start:workspace": "yarn workspace @standardnotes/workspace-server start",
|
"start:workspace": "yarn workspace @standardnotes/workspace-server start",
|
||||||
"start:analytics": "yarn workspace @standardnotes/analytics worker",
|
"start:analytics": "yarn workspace @standardnotes/analytics worker",
|
||||||
|
"start:revisions": "yarn workspace @standardnotes/revisions-server start",
|
||||||
"release": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish new version\"",
|
"release": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish new version\"",
|
||||||
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||||
"postversion": "./scripts/push-tags-one-by-one.sh",
|
"postversion": "./scripts/push-tags-one-by-one.sh",
|
||||||
@@ -46,8 +48,8 @@
|
|||||||
"@lerna-lite/list": "^1.5.1",
|
"@lerna-lite/list": "^1.5.1",
|
||||||
"@lerna-lite/run": "^1.5.1",
|
"@lerna-lite/run": "^1.5.1",
|
||||||
"@types/jest": "^29.1.1",
|
"@types/jest": "^29.1.1",
|
||||||
"@types/newrelic": "^7.0.3",
|
"@types/newrelic": "^7.0.4",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.11.9",
|
||||||
"@typescript-eslint/parser": "^5.40.1",
|
"@typescript-eslint/parser": "^5.40.1",
|
||||||
"eslint": "^8.17.0",
|
"eslint": "^8.17.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
@@ -59,7 +61,7 @@
|
|||||||
},
|
},
|
||||||
"packageManager": "yarn@4.0.0-rc.25",
|
"packageManager": "yarn@4.0.0-rc.25",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/node": "^7.3.0",
|
"@sentry/node": "^7.19.0",
|
||||||
"newrelic": "^9.0.0"
|
"newrelic": "^9.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,306 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.12.25](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.24...@standardnotes/analytics@2.12.25) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.24](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.23...@standardnotes/analytics@2.12.24) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** daily analytics report template ([41906ec](https://github.com/standardnotes/server/commit/41906ec2f9fd4d605b1c002826173e14fb534e00))
|
||||||
|
|
||||||
|
## [2.12.23](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.22...@standardnotes/analytics@2.12.23) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.22](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.21...@standardnotes/analytics@2.12.22) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** report event publishing ([a605660](https://github.com/standardnotes/server/commit/a6056600eb96bf175189ad6d62870c9d736f331b))
|
||||||
|
|
||||||
|
## [2.12.21](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.20...@standardnotes/analytics@2.12.21) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add debug logs for report ([48c0cb5](https://github.com/standardnotes/server/commit/48c0cb5e62dc8af930de191deaa1eb3ff6c5a29f))
|
||||||
|
|
||||||
|
## [2.12.20](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.19...@standardnotes/analytics@2.12.20) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.19](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.18...@standardnotes/analytics@2.12.19) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.18](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.17...@standardnotes/analytics@2.12.18) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.16...@standardnotes/analytics@2.12.17) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.15...@standardnotes/analytics@2.12.16) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.14...@standardnotes/analytics@2.12.15) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.13...@standardnotes/analytics@2.12.14) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.12...@standardnotes/analytics@2.12.13) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.11...@standardnotes/analytics@2.12.12) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.10...@standardnotes/analytics@2.12.11) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.9...@standardnotes/analytics@2.12.10) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.8...@standardnotes/analytics@2.12.9) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.7...@standardnotes/analytics@2.12.8) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.6...@standardnotes/analytics@2.12.7) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.5...@standardnotes/analytics@2.12.6) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.4...@standardnotes/analytics@2.12.5) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.3...@standardnotes/analytics@2.12.4) (2022-12-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.2...@standardnotes/analytics@2.12.3) (2022-12-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.1...@standardnotes/analytics@2.12.2) (2022-12-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.0...@standardnotes/analytics@2.12.1) (2022-12-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
# [2.12.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.17...@standardnotes/analytics@2.12.0) (2022-12-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **domain-core:** distinguish between username and email ([06fd404](https://github.com/standardnotes/server/commit/06fd404d44b44a53733f889aabd4da63f21e2f36))
|
||||||
|
|
||||||
|
## [2.11.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.16...@standardnotes/analytics@2.11.17) (2022-12-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.15...@standardnotes/analytics@2.11.16) (2022-12-02)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.14...@standardnotes/analytics@2.11.15) (2022-11-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.13...@standardnotes/analytics@2.11.14) (2022-11-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.12...@standardnotes/analytics@2.11.13) (2022-11-25)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.11...@standardnotes/analytics@2.11.12) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.10...@standardnotes/analytics@2.11.11) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.9...@standardnotes/analytics@2.11.10) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.8...@standardnotes/analytics@2.11.9) (2022-11-24)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.7...@standardnotes/analytics@2.11.8) (2022-11-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.6...@standardnotes/analytics@2.11.7) (2022-11-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* binding of sns and sqs with additional config ([74bc791](https://github.com/standardnotes/server/commit/74bc79116bc50d9a5af1a558db1b7108dcda6d0e))
|
||||||
|
|
||||||
|
## [2.11.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.5...@standardnotes/analytics@2.11.6) (2022-11-22)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.4...@standardnotes/analytics@2.11.5) (2022-11-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.3...@standardnotes/analytics@2.11.4) (2022-11-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.2...@standardnotes/analytics@2.11.3) (2022-11-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.11.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.1...@standardnotes/analytics@2.11.2) (2022-11-18)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** specs ([507d43b](https://github.com/standardnotes/server/commit/507d43b3289d1e178644df6d3e15d1d55e56c7bb))
|
||||||
|
|
||||||
|
## [2.11.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.0...@standardnotes/analytics@2.11.1) (2022-11-18)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* mapper interface imports ([1ec0723](https://github.com/standardnotes/server/commit/1ec072373d640c4e2f24b9bb12fec0c678b48032))
|
||||||
|
|
||||||
|
# [2.11.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.3...@standardnotes/analytics@2.11.0) (2022-11-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add publishing churn calculation values in the report ([6c43a33](https://github.com/standardnotes/server/commit/6c43a331d09c2dcf1300742509da6a1d8ef2f5b7))
|
||||||
|
|
||||||
|
## [2.10.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.2...@standardnotes/analytics@2.10.3) (2022-11-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** exclude five year plans from mrr stats ([fe1b2a0](https://github.com/standardnotes/server/commit/fe1b2a0e0744417e592f3f61f42610765b416ce6))
|
||||||
|
|
||||||
|
## [2.10.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.1...@standardnotes/analytics@2.10.2) (2022-11-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** imports from domain-core ([15dfd6d](https://github.com/standardnotes/server/commit/15dfd6dcba75a772000eeb01b78a532067b01d5b))
|
||||||
|
|
||||||
|
## [2.10.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.0...@standardnotes/analytics@2.10.1) (2022-11-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** retrieving revisions ([50f7ae3](https://github.com/standardnotes/server/commit/50f7ae338ad66d3465fa16c31e7c47c57b1e0c3c))
|
||||||
|
|
||||||
|
# [2.10.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.9...@standardnotes/analytics@2.10.0) (2022-11-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** extract domain core into a separate package ([0f94e2a](https://github.com/standardnotes/server/commit/0f94e2ad0c8927733eac31f130cbe649dce765f9))
|
||||||
|
|
||||||
|
## [2.9.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.6...@standardnotes/analytics@2.9.9) (2022-11-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** bump version ([8715fe1](https://github.com/standardnotes/server/commit/8715fe182221f67f02b5f49e566366db3db581f4))
|
||||||
|
|
||||||
|
## [2.9.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.5...@standardnotes/analytics@2.9.6) (2022-11-14)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.9.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.4...@standardnotes/analytics@2.9.5) (2022-11-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.9.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.3...@standardnotes/analytics@2.9.4) (2022-11-11)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.9.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.2...@standardnotes/analytics@2.9.3) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add five year plans mrr calculation ([a03c5bc](https://github.com/standardnotes/server/commit/a03c5bceea2a9b166b1d5ad75181021462a86627))
|
||||||
|
|
||||||
|
## [2.9.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.1...@standardnotes/analytics@2.9.2) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add missing period for stats report ([9b593f2](https://github.com/standardnotes/server/commit/9b593f2a6b105ab8f9c7cef8bdda6892c42e20ef))
|
||||||
|
|
||||||
|
## [2.9.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.0...@standardnotes/analytics@2.9.1) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** generate mrr stats for last 30 days including Today ([b92c4ae](https://github.com/standardnotes/server/commit/b92c4ae650b53db5c0bb2a9cf9afb01caeb8d822))
|
||||||
|
|
||||||
|
# [2.9.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.8.3...@standardnotes/analytics@2.9.0) (2022-11-10)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add mrr for annual, monthly, pro and plus subscription plans ([ce3e259](https://github.com/standardnotes/server/commit/ce3e259bdedd10796fb4469f0eabd64bc326a115))
|
||||||
|
|
||||||
|
## [2.8.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.8.2...@standardnotes/analytics@2.8.3) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add subscription id to error logs ([81be065](https://github.com/standardnotes/server/commit/81be06598c918279f98a8ba6b59ea1b3803c949c))
|
||||||
|
|
||||||
|
## [2.8.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.8.1...@standardnotes/analytics@2.8.2) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add monthly mrr to the report ([fce47a0](https://github.com/standardnotes/server/commit/fce47a0a37a67b3edf3ea0b6ccda43c54dbd9870))
|
||||||
|
|
||||||
|
## [2.8.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.8.0...@standardnotes/analytics@2.8.1) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add persisting mrr for this month and this year as well ([8df0482](https://github.com/standardnotes/server/commit/8df0482eb4bfd63b9639fd786c9b6952ad7f801d))
|
||||||
|
|
||||||
|
# [2.8.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.7.3...@standardnotes/analytics@2.8.0) (2022-11-10)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add calculating monthly recurring revenue ([77e5065](https://github.com/standardnotes/server/commit/77e50655f6fa7f9c28e13f8b8bc6de246c0454f0))
|
||||||
|
|
||||||
|
## [2.7.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.7.2...@standardnotes/analytics@2.7.3) (2022-11-10)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** arhcitecture arrangements for use case execution ([7393954](https://github.com/standardnotes/server/commit/7393954ff6ece6143f7661104299172548db90ee))
|
||||||
|
|
||||||
|
## [2.7.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.7.1...@standardnotes/analytics@2.7.2) (2022-11-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** mrr column types ([90aef90](https://github.com/standardnotes/server/commit/90aef905af05b8c1c86c7bd383df6b2b502f7c91))
|
||||||
|
|
||||||
|
## [2.7.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.7.0...@standardnotes/analytics@2.7.1) (2022-11-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add missing created at column ([89502be](https://github.com/standardnotes/server/commit/89502bed638b17301e42e0d5916635b0a59f585d))
|
||||||
|
|
||||||
# [2.7.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.6.0...@standardnotes/analytics@2.7.0) (2022-11-09)
|
# [2.7.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.6.0...@standardnotes/analytics@2.7.0) (2022-11-09)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:16.15.1-alpine
|
FROM node:18.12.1-alpine
|
||||||
|
|
||||||
RUN apk add --update \
|
RUN apk add --update \
|
||||||
curl \
|
curl \
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -15,6 +16,9 @@ import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
|||||||
import TYPES from '../src/Bootstrap/Types'
|
import TYPES from '../src/Bootstrap/Types'
|
||||||
import { Env } from '../src/Bootstrap/Env'
|
import { Env } from '../src/Bootstrap/Env'
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
|
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||||
|
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
const requestReport = async (
|
const requestReport = async (
|
||||||
analyticsStore: AnalyticsStoreInterface,
|
analyticsStore: AnalyticsStoreInterface,
|
||||||
@@ -22,7 +26,12 @@ const requestReport = async (
|
|||||||
domainEventFactory: DomainEventFactoryInterface,
|
domainEventFactory: DomainEventFactoryInterface,
|
||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||||
|
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
|
||||||
|
timer: TimerInterface,
|
||||||
|
adminEmails: string[],
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
await calculateMonthlyRecurringRevenue.execute({})
|
||||||
|
|
||||||
const analyticsOverTime: Array<{
|
const analyticsOverTime: Array<{
|
||||||
name: string
|
name: string
|
||||||
period: number
|
period: number
|
||||||
@@ -96,6 +105,40 @@ const requestReport = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statisticsOverTime: Array<{
|
||||||
|
name: string
|
||||||
|
period: number
|
||||||
|
counts: Array<{
|
||||||
|
periodKey: string
|
||||||
|
totalCount: number
|
||||||
|
}>
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
const thirtyDaysStatisticsNames = [
|
||||||
|
StatisticsMeasure.MRR,
|
||||||
|
StatisticsMeasure.AnnualPlansMRR,
|
||||||
|
StatisticsMeasure.MonthlyPlansMRR,
|
||||||
|
StatisticsMeasure.FiveYearPlansMRR,
|
||||||
|
StatisticsMeasure.PlusPlansMRR,
|
||||||
|
StatisticsMeasure.ProPlansMRR,
|
||||||
|
]
|
||||||
|
for (const statisticName of thirtyDaysStatisticsNames) {
|
||||||
|
statisticsOverTime.push({
|
||||||
|
name: statisticName,
|
||||||
|
period: Period.Last30DaysIncludingToday,
|
||||||
|
counts: await statisticsStore.calculateTotalCountOverPeriod(statisticName, Period.Last30DaysIncludingToday),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthlyStatisticsNames = [StatisticsMeasure.MRR]
|
||||||
|
for (const statisticName of monthlyStatisticsNames) {
|
||||||
|
statisticsOverTime.push({
|
||||||
|
name: statisticName,
|
||||||
|
period: Period.ThisYear,
|
||||||
|
counts: await statisticsStore.calculateTotalCountOverPeriod(statisticName, Period.ThisYear),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const statisticMeasureNames = [
|
const statisticMeasureNames = [
|
||||||
StatisticsMeasure.Income,
|
StatisticsMeasure.Income,
|
||||||
StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome,
|
StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome,
|
||||||
@@ -137,6 +180,9 @@ const requestReport = async (
|
|||||||
const churnRates: Array<{
|
const churnRates: Array<{
|
||||||
rate: number
|
rate: number
|
||||||
periodKey: string
|
periodKey: string
|
||||||
|
averageCustomersCount: number
|
||||||
|
existingCustomersChurn: number
|
||||||
|
newCustomersChurn: number
|
||||||
}> = []
|
}> = []
|
||||||
for (const monthPeriodKey of monthlyPeriodKeys) {
|
for (const monthPeriodKey of monthlyPeriodKeys) {
|
||||||
const monthPeriod = periodKeyGenerator.convertPeriodKeyToPeriod(monthPeriodKey)
|
const monthPeriod = periodKeyGenerator.convertPeriodKeyToPeriod(monthPeriodKey)
|
||||||
@@ -166,24 +212,35 @@ const requestReport = async (
|
|||||||
churnRates.push({
|
churnRates.push({
|
||||||
periodKey: monthPeriodKey,
|
periodKey: monthPeriodKey,
|
||||||
rate: averageCustomersCount ? (totalChurn / averageCustomersCount) * 100 : 0,
|
rate: averageCustomersCount ? (totalChurn / averageCustomersCount) * 100 : 0,
|
||||||
|
averageCustomersCount,
|
||||||
|
existingCustomersChurn,
|
||||||
|
newCustomersChurn,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = domainEventFactory.createDailyAnalyticsReportGeneratedEvent({
|
for (const adminEmail of adminEmails) {
|
||||||
applicationStatistics: await statisticsStore.getYesterdayApplicationUsage(),
|
await domainEventPublisher.publish(
|
||||||
snjsStatistics: await statisticsStore.getYesterdaySNJSUsage(),
|
domainEventFactory.createEmailRequestedEvent({
|
||||||
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
|
messageIdentifier: 'VERSION_ADOPTION_REPORT',
|
||||||
activityStatistics: yesterdayActivityStatistics,
|
subject: getSubject(),
|
||||||
activityStatisticsOverTime: analyticsOverTime,
|
body: getBody(
|
||||||
statisticMeasures,
|
{
|
||||||
retentionStatistics: [],
|
activityStatistics: yesterdayActivityStatistics,
|
||||||
churn: {
|
activityStatisticsOverTime: analyticsOverTime,
|
||||||
periodKeys: monthlyPeriodKeys,
|
statisticsOverTime,
|
||||||
values: churnRates,
|
statisticMeasures,
|
||||||
},
|
churn: {
|
||||||
})
|
periodKeys: monthlyPeriodKeys,
|
||||||
|
values: churnRates,
|
||||||
await domainEventPublisher.publish(event)
|
},
|
||||||
|
},
|
||||||
|
timer,
|
||||||
|
),
|
||||||
|
level: EmailLevel.LEVELS.System,
|
||||||
|
userEmail: adminEmail,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = new ContainerConfigLoader()
|
const container = new ContainerConfigLoader()
|
||||||
@@ -200,9 +257,25 @@ 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(
|
||||||
|
TYPES.CalculateMonthlyRecurringRevenue,
|
||||||
|
)
|
||||||
|
const adminEmails = container.get(TYPES.ADMIN_EMAILS) as string[]
|
||||||
|
|
||||||
|
logger.info(`Sending report to following admins: ${adminEmails}`)
|
||||||
|
|
||||||
Promise.resolve(
|
Promise.resolve(
|
||||||
requestReport(analyticsStore, statisticsStore, domainEventFactory, domainEventPublisher, periodKeyGenerator),
|
requestReport(
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
domainEventFactory,
|
||||||
|
domainEventPublisher,
|
||||||
|
periodKeyGenerator,
|
||||||
|
calculateMonthlyRecurringRevenue,
|
||||||
|
timer,
|
||||||
|
adminEmails,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('Usage report generation complete')
|
logger.info('Usage report generation complete')
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ COMMAND=$1 && shift 1
|
|||||||
|
|
||||||
case "$COMMAND" in
|
case "$COMMAND" in
|
||||||
'start-worker' )
|
'start-worker' )
|
||||||
echo "Starting Worker..."
|
echo "[Docker] Starting Worker..."
|
||||||
yarn workspace @standardnotes/analytics worker
|
yarn workspace @standardnotes/analytics worker
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'report' )
|
'report' )
|
||||||
echo "Starting Usage Report Generation..."
|
echo "[Docker] Starting Usage Report Generation..."
|
||||||
yarn workspace @standardnotes/analytics report
|
yarn workspace @standardnotes/analytics report
|
||||||
;;
|
;;
|
||||||
|
|
||||||
* )
|
* )
|
||||||
echo "Unknown command"
|
echo "[Docker] Unknown command"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
...tsjPreset.transform,
|
...tsjPreset.transform,
|
||||||
},
|
},
|
||||||
coveragePathIgnorePatterns: ['/Infra/'],
|
coveragePathIgnorePatterns: ['/Infra/', '/Domain/Email/'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class addMissingCreatedAt1667994036734 implements MigrationInterface {
|
||||||
|
name = 'addMissingCreatedAt1667994036734'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `created_at` bigint NOT NULL')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `created_at`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class fixMrrFloatingColumns1667995681714 implements MigrationInterface {
|
||||||
|
name = 'fixMrrFloatingColumns1667995681714'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `previous_mrr`')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `previous_mrr` float NOT NULL')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `new_mrr`')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `new_mrr` float NOT NULL')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `new_mrr`')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `new_mrr` int NOT NULL')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` DROP COLUMN `previous_mrr`')
|
||||||
|
await queryRunner.query('ALTER TABLE `revenue_modifications` ADD `previous_mrr` int NOT NULL')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "2.7.0",
|
"version": "2.12.25",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0 <17.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Analytics tools for Standard Notes projects",
|
"description": "Analytics tools for Standard Notes projects",
|
||||||
@@ -25,11 +25,10 @@
|
|||||||
"typeorm": "typeorm-ts-node-commonjs"
|
"typeorm": "typeorm-ts-node-commonjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/ioredis": "^4.28.10",
|
"@types/ioredis": "^5.0.0",
|
||||||
"@types/jest": "^29.1.1",
|
"@types/jest": "^29.1.1",
|
||||||
"@types/newrelic": "^7.0.3",
|
"@types/newrelic": "^7.0.4",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.11.9",
|
||||||
"@types/uuid": "^8.3.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||||
"eslint": "^8.14.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
@@ -39,22 +38,21 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@newrelic/winston-enricher": "^4.0.0",
|
"@newrelic/winston-enricher": "^4.0.0",
|
||||||
"@sentry/node": "^7.3.0",
|
"@sentry/node": "^7.19.0",
|
||||||
"@standardnotes/common": "workspace:*",
|
"@standardnotes/common": "workspace:*",
|
||||||
|
"@standardnotes/domain-core": "workspace:^",
|
||||||
"@standardnotes/domain-events": "workspace:*",
|
"@standardnotes/domain-events": "workspace:*",
|
||||||
"@standardnotes/domain-events-infra": "workspace:*",
|
"@standardnotes/domain-events-infra": "workspace:*",
|
||||||
"@standardnotes/time": "workspace:*",
|
"@standardnotes/time": "workspace:*",
|
||||||
"aws-sdk": "^2.1158.0",
|
"aws-sdk": "^2.1260.0",
|
||||||
"dayjs": "^1.11.6",
|
"dayjs": "^1.11.6",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"inversify": "^6.0.1",
|
"inversify": "^6.0.1",
|
||||||
"ioredis": "^5.2.3",
|
"ioredis": "^5.2.4",
|
||||||
"mysql2": "^2.3.3",
|
"mysql2": "^2.3.3",
|
||||||
"newrelic": "^9.0.0",
|
"newrelic": "^9.6.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"shallow-equal-object": "^1.1.1",
|
"typeorm": "^0.3.10",
|
||||||
"typeorm": "^0.3.6",
|
|
||||||
"uuid": "^9.0.0",
|
|
||||||
"winston": "^3.8.1"
|
"winston": "^3.8.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
DomainEventMessageHandlerInterface,
|
DomainEventMessageHandlerInterface,
|
||||||
DomainEventSubscriberFactoryInterface,
|
DomainEventSubscriberFactoryInterface,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
|
import { MapperInterface } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { Env } from './Env'
|
import { Env } from './Env'
|
||||||
import TYPES from './Types'
|
import TYPES from './Types'
|
||||||
@@ -47,10 +48,10 @@ import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEv
|
|||||||
import { RevenueModificationRepositoryInterface } from '../Domain/Revenue/RevenueModificationRepositoryInterface'
|
import { RevenueModificationRepositoryInterface } from '../Domain/Revenue/RevenueModificationRepositoryInterface'
|
||||||
import { MySQLRevenueModificationRepository } from '../Infra/MySQL/MySQLRevenueModificationRepository'
|
import { MySQLRevenueModificationRepository } from '../Infra/MySQL/MySQLRevenueModificationRepository'
|
||||||
import { TypeORMRevenueModification } from '../Infra/TypeORM/TypeORMRevenueModification'
|
import { TypeORMRevenueModification } from '../Infra/TypeORM/TypeORMRevenueModification'
|
||||||
import { MapInterface } from '../Domain/Map/MapInterface'
|
|
||||||
import { RevenueModification } from '../Domain/Revenue/RevenueModification'
|
import { RevenueModification } from '../Domain/Revenue/RevenueModification'
|
||||||
import { RevenueModificationMap } from '../Domain/Map/RevenueModificationMap'
|
import { RevenueModificationMap } from '../Domain/Map/RevenueModificationMap'
|
||||||
import { SaveRevenueModification } from '../Domain/UseCase/SaveRevenueModification/SaveRevenueModification'
|
import { SaveRevenueModification } from '../Domain/UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { CalculateMonthlyRecurringRevenue } from '../Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||||
|
|
||||||
// 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')
|
||||||
@@ -88,13 +89,24 @@ export class ContainerConfigLoader {
|
|||||||
})
|
})
|
||||||
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
||||||
|
|
||||||
if (env.get('SNS_AWS_REGION', true)) {
|
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||||
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(
|
const snsConfig: AWS.SNS.Types.ClientConfiguration = {
|
||||||
new AWS.SNS({
|
apiVersion: 'latest',
|
||||||
apiVersion: 'latest',
|
region: env.get('SNS_AWS_REGION', true),
|
||||||
region: env.get('SNS_AWS_REGION', true),
|
}
|
||||||
}),
|
if (env.get('SNS_ENDPOINT', true)) {
|
||||||
)
|
snsConfig.endpoint = env.get('SNS_ENDPOINT', true)
|
||||||
|
}
|
||||||
|
if (env.get('SNS_DISABLE_SSL', true) === 'true') {
|
||||||
|
snsConfig.sslEnabled = false
|
||||||
|
}
|
||||||
|
if (env.get('SNS_ACCESS_KEY_ID', true) && env.get('SNS_SECRET_ACCESS_KEY', true)) {
|
||||||
|
snsConfig.credentials = {
|
||||||
|
accessKeyId: env.get('SNS_ACCESS_KEY_ID', true),
|
||||||
|
secretAccessKey: env.get('SNS_SECRET_ACCESS_KEY', true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(new AWS.SNS(snsConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env.get('SQS_QUEUE_URL', true)) {
|
if (env.get('SQS_QUEUE_URL', true)) {
|
||||||
@@ -118,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
|
||||||
@@ -138,6 +151,9 @@ export class ContainerConfigLoader {
|
|||||||
// Use Case
|
// Use Case
|
||||||
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
||||||
container.bind<SaveRevenueModification>(TYPES.SaveRevenueModification).to(SaveRevenueModification)
|
container.bind<SaveRevenueModification>(TYPES.SaveRevenueModification).to(SaveRevenueModification)
|
||||||
|
container
|
||||||
|
.bind<CalculateMonthlyRecurringRevenue>(TYPES.CalculateMonthlyRecurringRevenue)
|
||||||
|
.to(CalculateMonthlyRecurringRevenue)
|
||||||
|
|
||||||
// Hanlders
|
// Hanlders
|
||||||
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||||
@@ -168,7 +184,7 @@ export class ContainerConfigLoader {
|
|||||||
|
|
||||||
// Maps
|
// Maps
|
||||||
container
|
container
|
||||||
.bind<MapInterface<RevenueModification, TypeORMRevenueModification>>(TYPES.RevenueModificationMap)
|
.bind<MapperInterface<RevenueModification, TypeORMRevenueModification>>(TYPES.RevenueModificationMap)
|
||||||
.to(RevenueModificationMap)
|
.to(RevenueModificationMap)
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
|
|||||||
@@ -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'),
|
||||||
@@ -20,6 +21,7 @@ const TYPES = {
|
|||||||
// Use Case
|
// Use Case
|
||||||
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
||||||
SaveRevenueModification: Symbol.for('SaveRevenueModification'),
|
SaveRevenueModification: Symbol.for('SaveRevenueModification'),
|
||||||
|
CalculateMonthlyRecurringRevenue: Symbol.for('CalculateMonthlyRecurringRevenue'),
|
||||||
// Handlers
|
// Handlers
|
||||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
|
import { html } from './daily-analytics-report.html'
|
||||||
|
|
||||||
|
export function getSubject(): string {
|
||||||
|
return `Daily analytics report ${new Date().toLocaleDateString('en-US')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(data: unknown, timer: TimerInterface): string {
|
||||||
|
return html(data, timer)
|
||||||
|
}
|
||||||
@@ -0,0 +1,966 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
|
||||||
|
const getChartUrls = (
|
||||||
|
data: any,
|
||||||
|
): {
|
||||||
|
subscriptions: string
|
||||||
|
users: string
|
||||||
|
quarterlyPerformance: string
|
||||||
|
churn: string
|
||||||
|
mrr: string
|
||||||
|
mrrMonthly: string
|
||||||
|
} => {
|
||||||
|
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionsLinerOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: subscriptionPurchasingOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Subscription Purchases',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: subscriptionPurchasingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Renewals',
|
||||||
|
backgroundColor: 'rgb(54, 162, 235)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
data: subscriptionRenewingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Refunds',
|
||||||
|
backgroundColor: 'rgb(255, 221, 51)',
|
||||||
|
borderColor: 'rgb(255, 221, 51)',
|
||||||
|
data: subscriptionRefundingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Cancels',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: subscriptionCancelledOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Reactivations',
|
||||||
|
backgroundColor: 'rgb(221, 51, 255)',
|
||||||
|
borderColor: 'rgb(221, 51, 255)',
|
||||||
|
data: subscriptionReactivatedOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userDeletionOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
|
||||||
|
const usersLinerOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: userRegistrationOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'User Registrations',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: userRegistrationOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Account Deletions',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: userDeletionOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const quarters = [Period.Q1ThisYear, Period.Q2ThisYear, Period.Q3ThisYear, Period.Q4ThisYear]
|
||||||
|
const quarterlyUserRegistrations = []
|
||||||
|
const quarterlySubscriptionPurchases = []
|
||||||
|
const quarterlySubscriptionRenewals = []
|
||||||
|
for (const quarter of quarters) {
|
||||||
|
const registrations =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
const purchases =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
const renewals =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
quarterlyUserRegistrations.push(registrations)
|
||||||
|
quarterlySubscriptionPurchases.push(purchases)
|
||||||
|
quarterlySubscriptionRenewals.push(renewals)
|
||||||
|
}
|
||||||
|
|
||||||
|
const quarterlyConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'User Registrations',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlyUserRegistrations,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Purchases',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.5)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlySubscriptionPurchases,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Renewals',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140, 0.5)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlySubscriptionRenewals,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Quarterly Performance',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthlyChurnRates = data.churn.values.map((value: { rate: number }) => +value.rate.toFixed(2))
|
||||||
|
|
||||||
|
const churnConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Churn Percent',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: monthlyChurnRates,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Monthly Churn Rate',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
|
||||||
|
const mrrOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: mrrOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'MRR',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: mrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Monthly Plans',
|
||||||
|
backgroundColor: 'rgb(54, 162, 235)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
data: monthlyPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Annual Plans',
|
||||||
|
backgroundColor: 'rgb(255, 221, 51)',
|
||||||
|
borderColor: 'rgb(255, 221, 51)',
|
||||||
|
data: annualPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Five Year Plans',
|
||||||
|
backgroundColor: 'rgb(255, 120, 120)',
|
||||||
|
borderColor: 'rgb(255, 120, 120)',
|
||||||
|
data: fiveYearPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - PRO Plans',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: proPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - PLUS Plans',
|
||||||
|
backgroundColor: 'rgb(221, 51, 255)',
|
||||||
|
borderColor: 'rgb(221, 51, 255)',
|
||||||
|
data: plusPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mrrMonthlyOverTime = data.statisticsOverTime
|
||||||
|
.find((a: { name: string; period: Period }) => a.name === 'mrr' && a.period === Period.ThisYear)
|
||||||
|
?.counts.map((count: { totalCount: number }) => +count.totalCount.toFixed(2))
|
||||||
|
|
||||||
|
const mrrMonthlyConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'MRR',
|
||||||
|
backgroundColor: 'rgba(25, 255, 140, 0.5)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: mrrMonthlyOverTime,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Monthly MRR',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscriptions: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
|
||||||
|
JSON.stringify(subscriptionsLinerOverTimeConfig),
|
||||||
|
)}`,
|
||||||
|
users: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(usersLinerOverTimeConfig))}`,
|
||||||
|
quarterlyPerformance: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
|
||||||
|
JSON.stringify(quarterlyConfig),
|
||||||
|
)}`,
|
||||||
|
churn: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(churnConfig))}`,
|
||||||
|
mrr: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrOverTimeConfig))}`,
|
||||||
|
mrrMonthly: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrMonthlyConfig))}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const html = (data: any, timer: TimerInterface) => {
|
||||||
|
const chartUrls = getChartUrls(data)
|
||||||
|
|
||||||
|
const successfullPaymentsActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentSuccess && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const failedPaymentsActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentFailed && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const limitedDiscountPurchasedActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.LimitedDiscountOfferPurchased && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userDeletionOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const incomeMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Income && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const refundMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Refunds && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const incomeYesterday = incomeMeasureYesterday?.totalValue ?? 0
|
||||||
|
const refundsYesterday = refundMeasureYesterday?.totalValue ?? 0
|
||||||
|
const revenueYesterday = incomeYesterday - refundsYesterday
|
||||||
|
|
||||||
|
const subscriptionLengthMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(subscriptionLengthMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionRemainingTimePercentageMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionRemainingTimePercentageYesterday = Math.floor(
|
||||||
|
subscriptionRemainingTimePercentageMeasureYesterday?.average ?? 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationLengthMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const registrationLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationLengthMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationToSubscriptionMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const registrationToSubscriptionDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationToSubscriptionMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const incomeMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Income && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const refundMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Refunds && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const incomeThisMonth = incomeMeasureThisMonth?.totalValue ?? 0
|
||||||
|
const refundsThisMonth = refundMeasureThisMonth?.totalValue ?? 0
|
||||||
|
const revenueThisMonth = incomeThisMonth - refundsThisMonth
|
||||||
|
|
||||||
|
const subscriptionLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const subscriptionLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(subscriptionLengthMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionRemainingTimePercentageMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const subscriptionRemainingTimePercentageThisMonth = Math.floor(
|
||||||
|
subscriptionRemainingTimePercentageMeasureThisMonth?.average ?? 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const registrationLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationLengthMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationToSubscriptionMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const registrationToSubscriptionDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationToSubscriptionMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const plusSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
|
||||||
|
const mrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
|
||||||
|
const today = new Date()
|
||||||
|
const thisMonthPeriodKey = `${today.getFullYear().toString()}-${(today.getMonth() + 1).toString()}`
|
||||||
|
const thisMonthChurn = data.churn.values.find(
|
||||||
|
(value: { periodKey: string }) => value.periodKey === thisMonthPeriodKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ` <div>
|
||||||
|
<p>Hello,</p>
|
||||||
|
<p>
|
||||||
|
<strong>Here are some statistics from yesterday:</strong>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Payments</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Revenue: <b>$${revenueYesterday.toLocaleString('en-US')}</b> (Income: $
|
||||||
|
${incomeYesterday.toLocaleString('en-US')}, Refunds: $${refundsYesterday.toLocaleString('en-US')})
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Successfull payments: <b>${successfullPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Failed payments: <b>${failedPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>MRR Breakdown</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Total:</b> $${mrrOverTime?.counts[mrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>By Subscription Type:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>PLUS:</b> $
|
||||||
|
${plusPlansMrrOverTime?.counts[plusPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>PRO:</b> $
|
||||||
|
${proPlansMrrOverTime?.counts[proPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>By Billing Frequency:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Monthly:</b> $
|
||||||
|
${monthlyPlansMrrOverTime?.counts[monthlyPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Annual:</b> $
|
||||||
|
${annualPlansMrrOverTime?.counts[annualPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>5-year:</b> $
|
||||||
|
${fiveYearPlansMrrOverTime?.counts[fiveYearPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Income Breakdown</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Plus Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Pro Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Users</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of users registered:${' '}
|
||||||
|
<b>
|
||||||
|
${userRegistrationOverTime?.counts[userRegistrationOverTime?.counts.length - 1]?.totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of users unregistered:${' '}
|
||||||
|
<b>
|
||||||
|
${userDeletionOverTime?.counts[userDeletionOverTime?.counts.length - 1]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(average account duration: ${registrationLengthDurationYesterday.days} days${' '}
|
||||||
|
${registrationLengthDurationYesterday.hours} hours ${registrationLengthDurationYesterday.minutes} minutes)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Subscriptions</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions purchased:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionPurchasingOverTime?.counts[
|
||||||
|
subscriptionPurchasingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(includes <b>${limitedDiscountPurchasedActivity?.totalCount.toLocaleString('en-US')}</b> limited time
|
||||||
|
offer purchases)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions renewed:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionRenewingOverTime?.counts[
|
||||||
|
subscriptionRenewingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions refunded:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionRefundingOverTime?.counts[
|
||||||
|
subscriptionRefundingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions cancelled:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionCancelledOverTime?.counts[
|
||||||
|
subscriptionCancelledOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(average subscription duration: ${subscriptionLengthDurationYesterday.days} days${' '}
|
||||||
|
${subscriptionLengthDurationYesterday.hours} hours ${subscriptionLengthDurationYesterday.minutes} minutes,
|
||||||
|
average remaining subscription percentage: ${subscriptionRemainingTimePercentageYesterday}%)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions reactivated:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionReactivatedOverTime?.counts[
|
||||||
|
subscriptionReactivatedOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average time from registration to subscription purchase:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationToSubscriptionDurationYesterday.days} days${' '}
|
||||||
|
${registrationToSubscriptionDurationYesterday.hours} hours${' '}
|
||||||
|
${registrationToSubscriptionDurationYesterday.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<strong>Here are some statistics from last 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Payments (This Month)</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Revenue: <b>$${revenueThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Income: <b>$${incomeThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Refunds: <b>$${refundsThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Income Breakdown (This Month)</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Plus Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Pro Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Users</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of users registered: <b>${userRegistrationOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of users unregistered: <b>${userDeletionOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average account duration this month:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationLengthDurationThisMonth.days} days ${registrationLengthDurationThisMonth.hours} hours${' '}
|
||||||
|
${registrationLengthDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Subscriptions</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions purchased:${' '}
|
||||||
|
<b>${subscriptionPurchasingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions renewed:${' '}
|
||||||
|
<b>${subscriptionRenewingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions refunded:${' '}
|
||||||
|
<b>${subscriptionRefundingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions cancelled:${' '}
|
||||||
|
<b>${subscriptionCancelledOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions reactivated:${' '}
|
||||||
|
<b>${subscriptionReactivatedOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average subscription duration this month:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionLengthDurationThisMonth.days} days ${subscriptionLengthDurationThisMonth.hours} hours${' '}
|
||||||
|
${subscriptionLengthDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average subscription remaining percentage this month:${' '}
|
||||||
|
<b>${subscriptionRemainingTimePercentageThisMonth}%</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average time from registration to subscription purchase this month:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationToSubscriptionDurationThisMonth.days} days${' '}
|
||||||
|
${registrationToSubscriptionDurationThisMonth.hours} hours${' '}
|
||||||
|
${registrationToSubscriptionDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the MRR chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.mrr}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the MRR Monthly chart this year:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.mrrMonthly}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the subscription chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.subscriptions}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the users chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.users}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the monthly churn rate percentage:</strong>
|
||||||
|
</p>
|
||||||
|
<p>✅ GREAT! Up to 7% 🔶 OKAY: 8-10% 🩸 BAD: 11 -15 % 🚨 TERRIBLE! 16-20%</p>
|
||||||
|
<p>Churn is calculated by the following formula:</p>
|
||||||
|
<p>
|
||||||
|
( Existing Customers Churn [${thisMonthChurn?.existingCustomersChurn}] + New Customers Churn [
|
||||||
|
${thisMonthChurn?.newCustomersChurn}] ) * 100 / Average Customers Count This Month [
|
||||||
|
${thisMonthChurn?.averageCustomersCount}]
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.churn}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is quarterly performance chart:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.quarterlyPerformance}></img>
|
||||||
|
<p>Thanks,SN</p>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
@@ -18,5 +18,5 @@ export class AnalyticsEntity {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
@Index('email')
|
@Index('email')
|
||||||
declare userEmail: string
|
declare username: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
import 'reflect-metadata'
|
|
||||||
|
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
|
||||||
|
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
|
||||||
import { Period } from '../Time/Period'
|
|
||||||
|
|
||||||
import { DomainEventFactory } from './DomainEventFactory'
|
|
||||||
|
|
||||||
describe('DomainEventFactory', () => {
|
|
||||||
let timer: TimerInterface
|
|
||||||
|
|
||||||
const createFactory = () => new DomainEventFactory(timer)
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
timer = {} as jest.Mocked<TimerInterface>
|
|
||||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
|
|
||||||
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create a DAILY_ANALYTICS_REPORT_GENERATED event', () => {
|
|
||||||
expect(
|
|
||||||
createFactory().createDailyAnalyticsReportGeneratedEvent({
|
|
||||||
snjsStatistics: [
|
|
||||||
{
|
|
||||||
version: '1-2-3',
|
|
||||||
count: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
applicationStatistics: [
|
|
||||||
{
|
|
||||||
version: '2-3-4',
|
|
||||||
count: 45,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
activityStatistics: [
|
|
||||||
{
|
|
||||||
name: AnalyticsActivity.Register,
|
|
||||||
retention: 24,
|
|
||||||
totalCount: 45,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
statisticMeasures: [
|
|
||||||
{
|
|
||||||
name: StatisticsMeasure.Income,
|
|
||||||
totalValue: 43,
|
|
||||||
average: 23,
|
|
||||||
increments: 5,
|
|
||||||
period: Period.Today,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
activityStatisticsOverTime: [
|
|
||||||
{
|
|
||||||
name: AnalyticsActivity.Register,
|
|
||||||
period: Period.Last30Days,
|
|
||||||
counts: [
|
|
||||||
{
|
|
||||||
periodKey: '2022-10-9',
|
|
||||||
totalCount: 3,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
totalCount: 123,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
outOfSyncIncidents: 324,
|
|
||||||
retentionStatistics: [],
|
|
||||||
churn: {
|
|
||||||
periodKeys: ['2022-10-9'],
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
rate: 12,
|
|
||||||
periodKey: '2022-10-9',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
createdAt: expect.any(Date),
|
|
||||||
meta: {
|
|
||||||
correlation: {
|
|
||||||
userIdentifier: '',
|
|
||||||
userIdentifierType: 'uuid',
|
|
||||||
},
|
|
||||||
origin: 'analytics',
|
|
||||||
},
|
|
||||||
payload: {
|
|
||||||
activityStatistics: [
|
|
||||||
{
|
|
||||||
name: 'register',
|
|
||||||
retention: 24,
|
|
||||||
totalCount: 45,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
activityStatisticsOverTime: [
|
|
||||||
{
|
|
||||||
counts: [
|
|
||||||
{
|
|
||||||
periodKey: '2022-10-9',
|
|
||||||
totalCount: 3,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
name: 'register',
|
|
||||||
period: 9,
|
|
||||||
totalCount: 123,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
applicationStatistics: [
|
|
||||||
{
|
|
||||||
count: 45,
|
|
||||||
version: '2-3-4',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
churn: {
|
|
||||||
periodKeys: ['2022-10-9'],
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
periodKey: '2022-10-9',
|
|
||||||
rate: 12,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
outOfSyncIncidents: 324,
|
|
||||||
retentionStatistics: [],
|
|
||||||
snjsStatistics: [
|
|
||||||
{
|
|
||||||
count: 2,
|
|
||||||
version: '1-2-3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
statisticMeasures: [
|
|
||||||
{
|
|
||||||
average: 23,
|
|
||||||
increments: 5,
|
|
||||||
name: 'income',
|
|
||||||
period: 0,
|
|
||||||
totalValue: 43,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { DomainEventService, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
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'
|
||||||
@@ -7,65 +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
|
||||||
snjsStatistics: Array<{
|
messageIdentifier: string
|
||||||
version: string
|
level: string
|
||||||
count: number
|
body: string
|
||||||
}>
|
subject: string
|
||||||
applicationStatistics: Array<{
|
}): EmailRequestedEvent {
|
||||||
version: string
|
|
||||||
count: number
|
|
||||||
}>
|
|
||||||
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
|
|
||||||
}>
|
|
||||||
outOfSyncIncidents: number
|
|
||||||
retentionStatistics: Array<{
|
|
||||||
firstActivity: string
|
|
||||||
secondActivity: string
|
|
||||||
retention: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
firstPeriodKey: string
|
|
||||||
secondPeriodKey: string
|
|
||||||
value: number
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
churn: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
rate: number
|
|
||||||
periodKey: string
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}): 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,55 +1,11 @@
|
|||||||
import { DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
import { EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||||
|
|
||||||
export interface DomainEventFactoryInterface {
|
export interface DomainEventFactoryInterface {
|
||||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
createEmailRequestedEvent(dto: {
|
||||||
snjsStatistics: Array<{
|
userEmail: string
|
||||||
version: string
|
messageIdentifier: string
|
||||||
count: number
|
level: string
|
||||||
}>
|
body: string
|
||||||
applicationStatistics: Array<{
|
subject: string
|
||||||
version: string
|
}): EmailRequestedEvent
|
||||||
count: number
|
|
||||||
}>
|
|
||||||
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
|
|
||||||
}>
|
|
||||||
outOfSyncIncidents: number
|
|
||||||
retentionStatistics: Array<{
|
|
||||||
firstActivity: string
|
|
||||||
secondActivity: string
|
|
||||||
retention: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
firstPeriodKey: string
|
|
||||||
secondPeriodKey: string
|
|
||||||
value: number
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}>
|
|
||||||
churn: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
rate: number
|
|
||||||
periodKey: string
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}): DailyAnalyticsReportGeneratedEvent
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
import { SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
import { SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||||
|
|
||||||
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
||||||
@@ -9,9 +10,9 @@ import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
|||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
import { Result } from '../Core/Result'
|
|
||||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('SubscriptionCancelledEventHandler', () => {
|
describe('SubscriptionCancelledEventHandler', () => {
|
||||||
let event: SubscriptionCancelledEvent
|
let event: SubscriptionCancelledEvent
|
||||||
@@ -19,11 +20,21 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let statisticsStore: StatisticsStoreInterface
|
let statisticsStore: StatisticsStoreInterface
|
||||||
let saveRevenueModification: SaveRevenueModification
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createHandler = () =>
|
const createHandler = () =>
|
||||||
new SubscriptionCancelledEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore, saveRevenueModification)
|
new SubscriptionCancelledEventHandler(
|
||||||
|
getUserAnalyticsId,
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
saveRevenueModification,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||||
|
|
||||||
@@ -80,4 +91,14 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
||||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
event.payload.timestamp = 1642395451516000
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { Email } from '../Common/Email'
|
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
@@ -20,6 +21,7 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
|||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
||||||
@@ -32,16 +34,22 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
|||||||
|
|
||||||
await this.trackSubscriptionStatistics(event)
|
await this.trackSubscriptionStatistics(event)
|
||||||
|
|
||||||
await this.saveRevenueModification.execute({
|
const result = await this.saveRevenueModification.execute({
|
||||||
billingFrequency: event.payload.billingFrequency,
|
billingFrequency: event.payload.billingFrequency,
|
||||||
eventType: SubscriptionEventType.create(event.type).getValue(),
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async trackSubscriptionStatistics(event: SubscriptionCancelledEvent) {
|
private async trackSubscriptionStatistics(event: SubscriptionCancelledEvent) {
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import 'reflect-metadata'
|
|||||||
|
|
||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
import { SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { SubscriptionExpiredEventHandler } from './SubscriptionExpiredEventHandler'
|
import { SubscriptionExpiredEventHandler } from './SubscriptionExpiredEventHandler'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
import { Result } from '../Core/Result'
|
|
||||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('SubscriptionExpiredEventHandler', () => {
|
describe('SubscriptionExpiredEventHandler', () => {
|
||||||
let event: SubscriptionExpiredEvent
|
let event: SubscriptionExpiredEvent
|
||||||
@@ -17,11 +18,21 @@ describe('SubscriptionExpiredEventHandler', () => {
|
|||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let statisticsStore: StatisticsStoreInterface
|
let statisticsStore: StatisticsStoreInterface
|
||||||
let saveRevenueModification: SaveRevenueModification
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createHandler = () =>
|
const createHandler = () =>
|
||||||
new SubscriptionExpiredEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore, saveRevenueModification)
|
new SubscriptionExpiredEventHandler(
|
||||||
|
getUserAnalyticsId,
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
saveRevenueModification,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
event = {} as jest.Mocked<SubscriptionExpiredEvent>
|
event = {} as jest.Mocked<SubscriptionExpiredEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
event.type = 'SUBSCRIPTION_EXPIRED'
|
event.type = 'SUBSCRIPTION_EXPIRED'
|
||||||
@@ -57,4 +68,12 @@ describe('SubscriptionExpiredEventHandler', () => {
|
|||||||
expect(statisticsStore.setMeasure).toHaveBeenCalled()
|
expect(statisticsStore.setMeasure).toHaveBeenCalled()
|
||||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
import { DomainEventHandlerInterface, SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { Email } from '../Common/Email'
|
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
@@ -20,6 +21,7 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
|||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionExpiredEvent): Promise<void> {
|
async handle(event: SubscriptionExpiredEvent): Promise<void> {
|
||||||
@@ -36,15 +38,21 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
|||||||
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
||||||
)
|
)
|
||||||
|
|
||||||
await this.saveRevenueModification.execute({
|
const result = await this.saveRevenueModification.execute({
|
||||||
billingFrequency: event.payload.billingFrequency,
|
billingFrequency: event.payload.billingFrequency,
|
||||||
eventType: SubscriptionEventType.create(event.type).getValue(),
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'reflect-metadata'
|
|||||||
|
|
||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
import { SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { SubscriptionPurchasedEventHandler } from './SubscriptionPurchasedEventHandler'
|
import { SubscriptionPurchasedEventHandler } from './SubscriptionPurchasedEventHandler'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
@@ -9,8 +10,8 @@ import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
|||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
import { Result } from '../Core/Result'
|
|
||||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('SubscriptionPurchasedEventHandler', () => {
|
describe('SubscriptionPurchasedEventHandler', () => {
|
||||||
let event: SubscriptionPurchasedEvent
|
let event: SubscriptionPurchasedEvent
|
||||||
@@ -19,11 +20,21 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let statisticsStore: StatisticsStoreInterface
|
let statisticsStore: StatisticsStoreInterface
|
||||||
let saveRevenueModification: SaveRevenueModification
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createHandler = () =>
|
const createHandler = () =>
|
||||||
new SubscriptionPurchasedEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore, saveRevenueModification)
|
new SubscriptionPurchasedEventHandler(
|
||||||
|
getUserAnalyticsId,
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
saveRevenueModification,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
statisticsStore.incrementMeasure = jest.fn()
|
statisticsStore.incrementMeasure = jest.fn()
|
||||||
statisticsStore.setMeasure = jest.fn()
|
statisticsStore.setMeasure = jest.fn()
|
||||||
@@ -80,4 +91,12 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
|
|
||||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['limited-discount-offer-purchased'], 3, [Period.Today])
|
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['limited-discount-offer-purchased'], 3, [Period.Today])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
import { DomainEventHandlerInterface, SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { Email } from '../Common/Email'
|
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
@@ -20,6 +21,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
|||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
||||||
@@ -60,15 +62,21 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.saveRevenueModification.execute({
|
const result = await this.saveRevenueModification.execute({
|
||||||
billingFrequency: event.payload.billingFrequency,
|
billingFrequency: event.payload.billingFrequency,
|
||||||
eventType: SubscriptionEventType.create(event.type).getValue(),
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
newSubscriber: event.payload.newSubscriber,
|
newSubscriber: event.payload.newSubscriber,
|
||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'reflect-metadata'
|
|||||||
|
|
||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
import { SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
@@ -10,9 +11,9 @@ import { SubscriptionRefundedEventHandler } from './SubscriptionRefundedEventHan
|
|||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
import { Result } from '../Core/Result'
|
|
||||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('SubscriptionRefundedEventHandler', () => {
|
describe('SubscriptionRefundedEventHandler', () => {
|
||||||
let event: SubscriptionRefundedEvent
|
let event: SubscriptionRefundedEvent
|
||||||
@@ -20,11 +21,21 @@ describe('SubscriptionRefundedEventHandler', () => {
|
|||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let statisticsStore: StatisticsStoreInterface
|
let statisticsStore: StatisticsStoreInterface
|
||||||
let saveRevenueModification: SaveRevenueModification
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createHandler = () =>
|
const createHandler = () =>
|
||||||
new SubscriptionRefundedEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore, saveRevenueModification)
|
new SubscriptionRefundedEventHandler(
|
||||||
|
getUserAnalyticsId,
|
||||||
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
saveRevenueModification,
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
event = {} as jest.Mocked<SubscriptionRefundedEvent>
|
event = {} as jest.Mocked<SubscriptionRefundedEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
event.type = 'SUBSCRIPTION_REFUNDED'
|
event.type = 'SUBSCRIPTION_REFUNDED'
|
||||||
@@ -88,4 +99,12 @@ describe('SubscriptionRefundedEventHandler', () => {
|
|||||||
Period.ThisMonth,
|
Period.ThisMonth,
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
import { DomainEventHandlerInterface, SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { Email } from '../Common/Email'
|
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
@@ -20,6 +21,7 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
|||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionRefundedEvent): Promise<void> {
|
async handle(event: SubscriptionRefundedEvent): Promise<void> {
|
||||||
@@ -32,16 +34,22 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
|||||||
|
|
||||||
await this.markChurnActivity(analyticsId, event)
|
await this.markChurnActivity(analyticsId, event)
|
||||||
|
|
||||||
await this.saveRevenueModification.execute({
|
const result = await this.saveRevenueModification.execute({
|
||||||
billingFrequency: event.payload.billingFrequency,
|
billingFrequency: event.payload.billingFrequency,
|
||||||
eventType: SubscriptionEventType.create(event.type).getValue(),
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async markChurnActivity(analyticsId: number, event: SubscriptionRefundedEvent): Promise<void> {
|
private async markChurnActivity(analyticsId: number, event: SubscriptionRefundedEvent): Promise<void> {
|
||||||
|
|||||||
@@ -2,24 +2,29 @@ import 'reflect-metadata'
|
|||||||
|
|
||||||
import { SubscriptionName } from '@standardnotes/common'
|
import { SubscriptionName } from '@standardnotes/common'
|
||||||
import { SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
import { SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
|
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
import { Result } from '../Core/Result'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('SubscriptionRenewedEventHandler', () => {
|
describe('SubscriptionRenewedEventHandler', () => {
|
||||||
let event: SubscriptionRenewedEvent
|
let event: SubscriptionRenewedEvent
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let saveRevenueModification: SaveRevenueModification
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createHandler = () =>
|
const createHandler = () =>
|
||||||
new SubscriptionRenewedEventHandler(getUserAnalyticsId, analyticsStore, saveRevenueModification)
|
new SubscriptionRenewedEventHandler(getUserAnalyticsId, analyticsStore, saveRevenueModification, logger)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
|
|
||||||
event = {} as jest.Mocked<SubscriptionRenewedEvent>
|
event = {} as jest.Mocked<SubscriptionRenewedEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
event.type = 'SUBSCRIPTION_RENEWED'
|
event.type = 'SUBSCRIPTION_RENEWED'
|
||||||
@@ -52,4 +57,12 @@ describe('SubscriptionRenewedEventHandler', () => {
|
|||||||
expect(analyticsStore.unmarkActivity).toHaveBeenCalled()
|
expect(analyticsStore.unmarkActivity).toHaveBeenCalled()
|
||||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should log failure to save revenue modification', async () => {
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(logger.error).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
@@ -7,9 +8,9 @@ import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
|||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
import { Email } from '../Common/Email'
|
|
||||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
||||||
@@ -17,6 +18,7 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
|||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
||||||
@@ -32,15 +34,21 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
|||||||
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||||
)
|
)
|
||||||
|
|
||||||
await this.saveRevenueModification.execute({
|
const result = await this.saveRevenueModification.execute({
|
||||||
billingFrequency: event.payload.billingFrequency,
|
billingFrequency: event.payload.billingFrequency,
|
||||||
eventType: SubscriptionEventType.create(event.type).getValue(),
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
newSubscriber: false,
|
newSubscriber: false,
|
||||||
payedAmount: event.payload.payAmount,
|
payedAmount: event.payload.payAmount,
|
||||||
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
subscriptionId: event.payload.subscriptionId,
|
subscriptionId: event.payload.subscriptionId,
|
||||||
userEmail: Email.create(event.payload.userEmail).getValue(),
|
username: Username.create(event.payload.userEmail).getValue(),
|
||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
|||||||
async handle(event: UserRegisteredEvent): Promise<void> {
|
async handle(event: UserRegisteredEvent): Promise<void> {
|
||||||
let analyticsEntity = new AnalyticsEntity()
|
let analyticsEntity = new AnalyticsEntity()
|
||||||
analyticsEntity.userUuid = event.payload.userUuid
|
analyticsEntity.userUuid = event.payload.userUuid
|
||||||
analyticsEntity.userEmail = event.payload.email
|
analyticsEntity.username = event.payload.email
|
||||||
analyticsEntity = await this.analyticsEntityRepository.save(analyticsEntity)
|
analyticsEntity = await this.analyticsEntityRepository.save(analyticsEntity)
|
||||||
|
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsEntity.id, [
|
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsEntity.id, [
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface MapInterface<T, U> {
|
|
||||||
toDomain(persistence: U): T
|
|
||||||
toPersistence(domain: T): U
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
import { injectable } from 'inversify'
|
import { injectable } from 'inversify'
|
||||||
|
import { MapperInterface, UniqueEntityId, Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { TypeORMRevenueModification } from '../../Infra/TypeORM/TypeORMRevenueModification'
|
import { TypeORMRevenueModification } from '../../Infra/TypeORM/TypeORMRevenueModification'
|
||||||
import { UniqueEntityId } from '../Core/UniqueEntityId'
|
|
||||||
import { MonthlyRevenue } from '../Revenue/MonthlyRevenue'
|
import { MonthlyRevenue } from '../Revenue/MonthlyRevenue'
|
||||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
import { Subscription } from '../Subscription/Subscription'
|
import { Subscription } from '../Subscription/Subscription'
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
import { MapInterface } from './MapInterface'
|
|
||||||
import { Email } from '../Common/Email'
|
|
||||||
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class RevenueModificationMap implements MapInterface<RevenueModification, TypeORMRevenueModification> {
|
export class RevenueModificationMap implements MapperInterface<RevenueModification, TypeORMRevenueModification> {
|
||||||
toDomain(persistence: TypeORMRevenueModification): RevenueModification {
|
toDomain(persistence: TypeORMRevenueModification): RevenueModification {
|
||||||
const user = User.create(
|
const userOrError = User.create(
|
||||||
{
|
{
|
||||||
email: Email.create(persistence.userEmail).getValue(),
|
username: Username.create(persistence.username).getValue(),
|
||||||
},
|
},
|
||||||
new UniqueEntityId(persistence.userUuid),
|
new UniqueEntityId(persistence.userUuid),
|
||||||
)
|
)
|
||||||
const subscription = Subscription.create(
|
if (userOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not create user: ${userOrError.getError()}`)
|
||||||
|
}
|
||||||
|
const user = userOrError.getValue()
|
||||||
|
|
||||||
|
const subscriptionOrError = Subscription.create(
|
||||||
{
|
{
|
||||||
billingFrequency: persistence.billingFrequency,
|
billingFrequency: persistence.billingFrequency,
|
||||||
isFirstSubscriptionForUser: persistence.isNewCustomer,
|
isFirstSubscriptionForUser: persistence.isNewCustomer,
|
||||||
@@ -29,32 +32,47 @@ export class RevenueModificationMap implements MapInterface<RevenueModification,
|
|||||||
},
|
},
|
||||||
new UniqueEntityId(persistence.subscriptionId),
|
new UniqueEntityId(persistence.subscriptionId),
|
||||||
)
|
)
|
||||||
const previousMonthlyRevenueOrError = MonthlyRevenue.create(persistence.previousMonthlyRevenue)
|
if (subscriptionOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not create subscription: ${subscriptionOrError.getError()}`)
|
||||||
|
}
|
||||||
|
const subscription = subscriptionOrError.getValue()
|
||||||
|
|
||||||
return RevenueModification.create(
|
const previousMonthlyRevenueOrError = MonthlyRevenue.create(persistence.previousMonthlyRevenue)
|
||||||
|
const newMonthlyRevenueOrError = MonthlyRevenue.create(persistence.newMonthlyRevenue)
|
||||||
|
|
||||||
|
const revenuModificationOrError = RevenueModification.create(
|
||||||
{
|
{
|
||||||
user,
|
user,
|
||||||
subscription,
|
subscription,
|
||||||
eventType: SubscriptionEventType.create(persistence.eventType).getValue(),
|
eventType: SubscriptionEventType.create(persistence.eventType).getValue(),
|
||||||
previousMonthlyRevenue: previousMonthlyRevenueOrError.getValue(),
|
previousMonthlyRevenue: previousMonthlyRevenueOrError.getValue(),
|
||||||
|
newMonthlyRevenue: newMonthlyRevenueOrError.getValue(),
|
||||||
|
createdAt: persistence.createdAt,
|
||||||
},
|
},
|
||||||
new UniqueEntityId(persistence.uuid),
|
new UniqueEntityId(persistence.uuid),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (revenuModificationOrError.isFailed()) {
|
||||||
|
throw new Error(`Could not map revenue modification to domain: ${revenuModificationOrError.getError()}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return revenuModificationOrError.getValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
toPersistence(domain: RevenueModification): TypeORMRevenueModification {
|
toProjection(domain: RevenueModification): TypeORMRevenueModification {
|
||||||
const { subscription, user } = domain.props
|
const { subscription, user } = domain.props
|
||||||
const persistence = new TypeORMRevenueModification()
|
const persistence = new TypeORMRevenueModification()
|
||||||
persistence.uuid = domain.id.toString()
|
persistence.uuid = domain.id.toString()
|
||||||
persistence.billingFrequency = subscription.props.billingFrequency
|
persistence.billingFrequency = subscription.props.billingFrequency
|
||||||
persistence.eventType = domain.props.eventType.value
|
persistence.eventType = domain.props.eventType.value
|
||||||
persistence.isNewCustomer = subscription.props.isFirstSubscriptionForUser
|
persistence.isNewCustomer = subscription.props.isFirstSubscriptionForUser
|
||||||
persistence.newMonthlyRevenue = domain.newMonthlyRevenue.value
|
persistence.newMonthlyRevenue = domain.props.newMonthlyRevenue.value
|
||||||
persistence.previousMonthlyRevenue = domain.props.previousMonthlyRevenue.value
|
persistence.previousMonthlyRevenue = domain.props.previousMonthlyRevenue.value
|
||||||
persistence.subscriptionId = subscription.id.toValue() as number
|
persistence.subscriptionId = subscription.id.toValue() as number
|
||||||
persistence.subscriptionPlan = subscription.props.planName.value
|
persistence.subscriptionPlan = subscription.props.planName.value
|
||||||
persistence.userEmail = user.props.email.value
|
persistence.username = user.props.username.value
|
||||||
persistence.userUuid = user.id.toString()
|
persistence.userUuid = user.id.toString()
|
||||||
|
persistence.createdAt = domain.props.createdAt
|
||||||
|
|
||||||
return persistence
|
return persistence
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ValueObject } from '../Core/ValueObject'
|
import { Result, ValueObject } from '@standardnotes/domain-core'
|
||||||
import { Result } from '../Core/Result'
|
|
||||||
import { MonthlyRevenueProps } from './MonthlyRevenueProps'
|
import { MonthlyRevenueProps } from './MonthlyRevenueProps'
|
||||||
|
|
||||||
export class MonthlyRevenue extends ValueObject<MonthlyRevenueProps> {
|
export class MonthlyRevenue extends ValueObject<MonthlyRevenueProps> {
|
||||||
@@ -13,7 +13,7 @@ export class MonthlyRevenue extends ValueObject<MonthlyRevenueProps> {
|
|||||||
|
|
||||||
static create(revenue: number): Result<MonthlyRevenue> {
|
static create(revenue: number): Result<MonthlyRevenue> {
|
||||||
if (isNaN(revenue) || revenue < 0) {
|
if (isNaN(revenue) || revenue < 0) {
|
||||||
return Result.fail<MonthlyRevenue>('Monthly revenue must be a non-negative number')
|
return Result.fail<MonthlyRevenue>(`Monthly revenue must be a non-negative number. Supplied: ${revenue}`)
|
||||||
} else {
|
} else {
|
||||||
return Result.ok<MonthlyRevenue>(new MonthlyRevenue({ value: revenue }))
|
return Result.ok<MonthlyRevenue>(new MonthlyRevenue({ value: revenue }))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Email } from '../Common/Email'
|
import { Username } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { Subscription } from '../Subscription/Subscription'
|
import { Subscription } from '../Subscription/Subscription'
|
||||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
@@ -16,46 +17,23 @@ describe('RevenueModification', () => {
|
|||||||
isFirstSubscriptionForUser: true,
|
isFirstSubscriptionForUser: true,
|
||||||
payedAmount: 123,
|
payedAmount: 123,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
})
|
}).getValue()
|
||||||
user = User.create({
|
user = User.create({
|
||||||
email: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
})
|
}).getValue()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should create an aggregate for purchased subscription', () => {
|
it('should create an aggregate for purchased subscription', () => {
|
||||||
const revenueModification = RevenueModification.create({
|
const revenueModification = RevenueModification.create({
|
||||||
|
createdAt: 2,
|
||||||
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
previousMonthlyRevenue: MonthlyRevenue.create(123).getValue(),
|
previousMonthlyRevenue: MonthlyRevenue.create(123).getValue(),
|
||||||
|
newMonthlyRevenue: MonthlyRevenue.create(45).getValue(),
|
||||||
subscription,
|
subscription,
|
||||||
user,
|
user,
|
||||||
})
|
}).getValue()
|
||||||
|
|
||||||
expect(revenueModification.id.toString()).toHaveLength(36)
|
expect(revenueModification.id.toString()).toHaveLength(36)
|
||||||
expect(revenueModification.newMonthlyRevenue.value).toEqual(123 / 12)
|
expect(revenueModification.props.newMonthlyRevenue.value).toEqual(45)
|
||||||
})
|
|
||||||
|
|
||||||
it('should create an aggregate for subscription expired', () => {
|
|
||||||
const revenueModification = RevenueModification.create({
|
|
||||||
createdAt: new Date(1),
|
|
||||||
eventType: SubscriptionEventType.create('SUBSCRIPTION_EXPIRED').getValue(),
|
|
||||||
previousMonthlyRevenue: MonthlyRevenue.create(123).getValue(),
|
|
||||||
subscription,
|
|
||||||
user,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(revenueModification.id.toString()).toHaveLength(36)
|
|
||||||
expect(revenueModification.newMonthlyRevenue.value).toEqual(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create an aggregate for subscription cancelled', () => {
|
|
||||||
const revenueModification = RevenueModification.create({
|
|
||||||
eventType: SubscriptionEventType.create('SUBSCRIPTION_CANCELLED').getValue(),
|
|
||||||
previousMonthlyRevenue: MonthlyRevenue.create(123).getValue(),
|
|
||||||
subscription,
|
|
||||||
user,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(revenueModification.id.toString()).toHaveLength(36)
|
|
||||||
expect(revenueModification.newMonthlyRevenue.value).toEqual(123)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Aggregate } from '../Core/Aggregate'
|
import { Aggregate, UniqueEntityId, Result } from '@standardnotes/domain-core'
|
||||||
import { UniqueEntityId } from '../Core/UniqueEntityId'
|
|
||||||
import { MonthlyRevenue } from './MonthlyRevenue'
|
|
||||||
import { RevenueModificationProps } from './RevenueModificationProps'
|
import { RevenueModificationProps } from './RevenueModificationProps'
|
||||||
|
|
||||||
export class RevenueModification extends Aggregate<RevenueModificationProps> {
|
export class RevenueModification extends Aggregate<RevenueModificationProps> {
|
||||||
@@ -8,38 +7,7 @@ export class RevenueModification extends Aggregate<RevenueModificationProps> {
|
|||||||
super(props, id)
|
super(props, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(props: RevenueModificationProps, id?: UniqueEntityId): RevenueModification {
|
static create(props: RevenueModificationProps, id?: UniqueEntityId): Result<RevenueModification> {
|
||||||
const revenueModification = new RevenueModification(
|
return Result.ok<RevenueModification>(new RevenueModification(props, id))
|
||||||
{
|
|
||||||
...props,
|
|
||||||
createdAt: props.createdAt ? props.createdAt : new Date(),
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
|
|
||||||
return revenueModification
|
|
||||||
}
|
|
||||||
|
|
||||||
get newMonthlyRevenue(): MonthlyRevenue {
|
|
||||||
const { subscription } = this.props
|
|
||||||
|
|
||||||
let revenue = 0
|
|
||||||
switch (this.props.eventType.value) {
|
|
||||||
case 'SUBSCRIPTION_PURCHASED':
|
|
||||||
case 'SUBSCRIPTION_RENEWED':
|
|
||||||
revenue = subscription.props.payedAmount / subscription.props.billingFrequency
|
|
||||||
break
|
|
||||||
case 'SUBSCRIPTION_EXPIRED':
|
|
||||||
case 'SUBSCRIPTION_REFUNDED':
|
|
||||||
revenue = 0
|
|
||||||
break
|
|
||||||
case 'SUBSCRIPTION_CANCELLED':
|
|
||||||
revenue = this.props.previousMonthlyRevenue.value
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
const monthlyRevenueOrError = MonthlyRevenue.create(revenue)
|
|
||||||
|
|
||||||
return monthlyRevenueOrError.getValue()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ export interface RevenueModificationProps {
|
|||||||
subscription: Subscription
|
subscription: Subscription
|
||||||
eventType: SubscriptionEventType
|
eventType: SubscriptionEventType
|
||||||
previousMonthlyRevenue: MonthlyRevenue
|
previousMonthlyRevenue: MonthlyRevenue
|
||||||
createdAt?: Date
|
newMonthlyRevenue: MonthlyRevenue
|
||||||
|
createdAt: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Uuid } from '../Common/Uuid'
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { RevenueModification } from './RevenueModification'
|
import { RevenueModification } from './RevenueModification'
|
||||||
|
|
||||||
export interface RevenueModificationRepositoryInterface {
|
export interface RevenueModificationRepositoryInterface {
|
||||||
findLastByUserUuid(userUuid: Uuid): Promise<RevenueModification | null>
|
findLastByUserUuid(userUuid: Uuid): Promise<RevenueModification | null>
|
||||||
|
sumMRRDiff(dto: { billingFrequencies: number[]; planNames?: string[] }): Promise<number>
|
||||||
save(revenueModification: RevenueModification): Promise<RevenueModification>
|
save(revenueModification: RevenueModification): Promise<RevenueModification>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,10 @@ export enum StatisticsMeasure {
|
|||||||
Refunds = 'refunds',
|
Refunds = 'refunds',
|
||||||
NewCustomers = 'new-customers',
|
NewCustomers = 'new-customers',
|
||||||
TotalCustomers = 'total-customers',
|
TotalCustomers = 'total-customers',
|
||||||
|
MRR = 'mrr',
|
||||||
|
MonthlyPlansMRR = 'monthly-plans-mrr',
|
||||||
|
AnnualPlansMRR = 'annual-plans-mrr',
|
||||||
|
FiveYearPlansMRR = 'five-year-plans-mrr',
|
||||||
|
ProPlansMRR = 'pro-plans-mrr',
|
||||||
|
PlusPlansMRR = 'plus-plans-mrr',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,8 @@ export interface StatisticsStoreInterface {
|
|||||||
getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number>
|
getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number>
|
||||||
getMeasureTotal(measure: StatisticsMeasure, periodOrPeriodKey: Period | string): Promise<number>
|
getMeasureTotal(measure: StatisticsMeasure, periodOrPeriodKey: Period | string): Promise<number>
|
||||||
getMeasureIncrementCounts(measure: StatisticsMeasure, period: Period): Promise<number>
|
getMeasureIncrementCounts(measure: StatisticsMeasure, period: Period): Promise<number>
|
||||||
|
calculateTotalCountOverPeriod(
|
||||||
|
measure: StatisticsMeasure,
|
||||||
|
period: Period,
|
||||||
|
): Promise<Array<{ periodKey: string; totalCount: number }>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ describe('Subscription', () => {
|
|||||||
isFirstSubscriptionForUser: true,
|
isFirstSubscriptionForUser: true,
|
||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
})
|
}).getValue()
|
||||||
|
|
||||||
expect(subscription.id.toString()).toHaveLength(36)
|
expect(subscription.id.toString()).toHaveLength(36)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Entity } from '../Core/Entity'
|
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||||
import { UniqueEntityId } from '../Core/UniqueEntityId'
|
|
||||||
import { SubscriptionProps } from './SubscriptionProps'
|
import { SubscriptionProps } from './SubscriptionProps'
|
||||||
|
|
||||||
export class Subscription extends Entity<SubscriptionProps> {
|
export class Subscription extends Entity<SubscriptionProps> {
|
||||||
@@ -11,7 +11,7 @@ export class Subscription extends Entity<SubscriptionProps> {
|
|||||||
super(props, id)
|
super(props, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(props: SubscriptionProps, id?: UniqueEntityId): Subscription {
|
static create(props: SubscriptionProps, id?: UniqueEntityId): Result<Subscription> {
|
||||||
return new Subscription(props, id)
|
return Result.ok<Subscription>(new Subscription(props, id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ValueObject } from '../Core/ValueObject'
|
import { ValueObject, Result } from '@standardnotes/domain-core'
|
||||||
import { Result } from '../Core/Result'
|
|
||||||
import { SubscriptionEventTypeProps } from './SubscriptionEventTypeProps'
|
import { SubscriptionEventTypeProps } from './SubscriptionEventTypeProps'
|
||||||
|
|
||||||
export class SubscriptionEventType extends ValueObject<SubscriptionEventTypeProps> {
|
export class SubscriptionEventType extends ValueObject<SubscriptionEventTypeProps> {
|
||||||
@@ -19,6 +19,7 @@ export class SubscriptionEventType extends ValueObject<SubscriptionEventTypeProp
|
|||||||
'SUBSCRIPTION_EXPIRED',
|
'SUBSCRIPTION_EXPIRED',
|
||||||
'SUBSCRIPTION_REFUNDED',
|
'SUBSCRIPTION_REFUNDED',
|
||||||
'SUBSCRIPTION_CANCELLED',
|
'SUBSCRIPTION_CANCELLED',
|
||||||
|
'SUBSCRIPTION_DATA_MIGRATED',
|
||||||
].includes(subscriptionEventType)
|
].includes(subscriptionEventType)
|
||||||
) {
|
) {
|
||||||
return Result.fail<SubscriptionEventType>(`Invalid subscription event type ${subscriptionEventType}`)
|
return Result.fail<SubscriptionEventType>(`Invalid subscription event type ${subscriptionEventType}`)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ValueObject } from '../Core/ValueObject'
|
import { Result, ValueObject } from '@standardnotes/domain-core'
|
||||||
import { Result } from '../Core/Result'
|
|
||||||
import { SubscriptionPlanNameProps } from './SubscriptionPlanNameProps'
|
import { SubscriptionPlanNameProps } from './SubscriptionPlanNameProps'
|
||||||
|
|
||||||
export class SubscriptionPlanName extends ValueObject<SubscriptionPlanNameProps> {
|
export class SubscriptionPlanName extends ValueObject<SubscriptionPlanNameProps> {
|
||||||
|
|||||||
@@ -26,4 +26,5 @@ export enum Period {
|
|||||||
OctoberThisYear,
|
OctoberThisYear,
|
||||||
NovemberThisYear,
|
NovemberThisYear,
|
||||||
DecemberThisYear,
|
DecemberThisYear,
|
||||||
|
Last30DaysIncludingToday,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,41 @@ describe('PeriodKeyGenerator', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should generate period keys for last 30 days including Today', () => {
|
||||||
|
expect(createGenerator().getDiscretePeriodKeys(Period.Last30DaysIncludingToday)).toEqual([
|
||||||
|
'2022-4-25',
|
||||||
|
'2022-4-26',
|
||||||
|
'2022-4-27',
|
||||||
|
'2022-4-28',
|
||||||
|
'2022-4-29',
|
||||||
|
'2022-4-30',
|
||||||
|
'2022-5-1',
|
||||||
|
'2022-5-2',
|
||||||
|
'2022-5-3',
|
||||||
|
'2022-5-4',
|
||||||
|
'2022-5-5',
|
||||||
|
'2022-5-6',
|
||||||
|
'2022-5-7',
|
||||||
|
'2022-5-8',
|
||||||
|
'2022-5-9',
|
||||||
|
'2022-5-10',
|
||||||
|
'2022-5-11',
|
||||||
|
'2022-5-12',
|
||||||
|
'2022-5-13',
|
||||||
|
'2022-5-14',
|
||||||
|
'2022-5-15',
|
||||||
|
'2022-5-16',
|
||||||
|
'2022-5-17',
|
||||||
|
'2022-5-18',
|
||||||
|
'2022-5-19',
|
||||||
|
'2022-5-20',
|
||||||
|
'2022-5-21',
|
||||||
|
'2022-5-22',
|
||||||
|
'2022-5-23',
|
||||||
|
'2022-5-24',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('should generate period keys for this year', () => {
|
it('should generate period keys for this year', () => {
|
||||||
expect(createGenerator().getDiscretePeriodKeys(Period.ThisYear)).toEqual([
|
expect(createGenerator().getDiscretePeriodKeys(Period.ThisYear)).toEqual([
|
||||||
'2022-1',
|
'2022-1',
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
|
|||||||
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return periodKeys
|
||||||
|
case Period.Last30DaysIncludingToday:
|
||||||
|
for (let i = 0; i <= 29; i++) {
|
||||||
|
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||||
|
}
|
||||||
|
|
||||||
return periodKeys
|
return periodKeys
|
||||||
case Period.Last7Days:
|
case Period.Last7Days:
|
||||||
for (let i = 1; i <= 7; i++) {
|
for (let i = 1; i <= 7; i++) {
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import { RevenueModificationRepositoryInterface } from '../../Revenue/RevenueModificationRepositoryInterface'
|
||||||
|
import { StatisticsMeasure } from '../../Statistics/StatisticsMeasure'
|
||||||
|
import { StatisticsStoreInterface } from '../../Statistics/StatisticsStoreInterface'
|
||||||
|
import { Period } from '../../Time/Period'
|
||||||
|
|
||||||
|
import { CalculateMonthlyRecurringRevenue } from './CalculateMonthlyRecurringRevenue'
|
||||||
|
|
||||||
|
describe('CalculateMonthlyRecurringRevenue', () => {
|
||||||
|
let revenueModificationRepository: RevenueModificationRepositoryInterface
|
||||||
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
|
||||||
|
const createUseCase = () => new CalculateMonthlyRecurringRevenue(revenueModificationRepository, statisticsStore)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
revenueModificationRepository = {} as jest.Mocked<RevenueModificationRepositoryInterface>
|
||||||
|
revenueModificationRepository.sumMRRDiff = jest.fn().mockReturnValue(123.45)
|
||||||
|
|
||||||
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
|
statisticsStore.setMeasure = jest.fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should calculate the MRR diff and persist it as a statistic', async () => {
|
||||||
|
await createUseCase().execute({})
|
||||||
|
|
||||||
|
expect(statisticsStore.setMeasure).toHaveBeenCalledWith(StatisticsMeasure.MRR, 123.45, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisMonth,
|
||||||
|
Period.ThisYear,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
import { SubscriptionBillingFrequency, SubscriptionName } from '@standardnotes/common'
|
||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
|
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
||||||
|
import { RevenueModificationRepositoryInterface } from '../../Revenue/RevenueModificationRepositoryInterface'
|
||||||
|
import { StatisticsMeasure } from '../../Statistics/StatisticsMeasure'
|
||||||
|
import { StatisticsStoreInterface } from '../../Statistics/StatisticsStoreInterface'
|
||||||
|
import { Period } from '../../Time/Period'
|
||||||
|
import { DomainUseCaseInterface } from '../DomainUseCaseInterface'
|
||||||
|
import { CalculateMonthlyRecurringRevenueDTO } from './CalculateMonthlyRecurringRevenueDTO'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class CalculateMonthlyRecurringRevenue implements DomainUseCaseInterface<MonthlyRevenue> {
|
||||||
|
constructor(
|
||||||
|
@inject(TYPES.RevenueModificationRepository)
|
||||||
|
private revenueModificationRepository: RevenueModificationRepositoryInterface,
|
||||||
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(_dto: CalculateMonthlyRecurringRevenueDTO): Promise<Result<MonthlyRevenue>> {
|
||||||
|
const mrrDiff = await this.revenueModificationRepository.sumMRRDiff({
|
||||||
|
billingFrequencies: [SubscriptionBillingFrequency.Annual, SubscriptionBillingFrequency.Monthly],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.statisticsStore.setMeasure(StatisticsMeasure.MRR, mrrDiff, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisMonth,
|
||||||
|
Period.ThisYear,
|
||||||
|
])
|
||||||
|
|
||||||
|
const monthlyPlansMrrDiff = await this.revenueModificationRepository.sumMRRDiff({
|
||||||
|
billingFrequencies: [SubscriptionBillingFrequency.Monthly],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.statisticsStore.setMeasure(StatisticsMeasure.MonthlyPlansMRR, monthlyPlansMrrDiff, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisMonth,
|
||||||
|
Period.ThisYear,
|
||||||
|
])
|
||||||
|
|
||||||
|
const annualPlansMrrDiff = await this.revenueModificationRepository.sumMRRDiff({
|
||||||
|
billingFrequencies: [SubscriptionBillingFrequency.Annual],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.statisticsStore.setMeasure(StatisticsMeasure.AnnualPlansMRR, annualPlansMrrDiff, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisMonth,
|
||||||
|
Period.ThisYear,
|
||||||
|
])
|
||||||
|
|
||||||
|
const fiveYearPlansMrrDiff = await this.revenueModificationRepository.sumMRRDiff({
|
||||||
|
billingFrequencies: [SubscriptionBillingFrequency.FiveYear],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.statisticsStore.setMeasure(StatisticsMeasure.FiveYearPlansMRR, fiveYearPlansMrrDiff, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisMonth,
|
||||||
|
Period.ThisYear,
|
||||||
|
])
|
||||||
|
|
||||||
|
const proPlansMrrDiff = await this.revenueModificationRepository.sumMRRDiff({
|
||||||
|
planNames: [SubscriptionName.ProPlan],
|
||||||
|
billingFrequencies: [SubscriptionBillingFrequency.Annual, SubscriptionBillingFrequency.Monthly],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.statisticsStore.setMeasure(StatisticsMeasure.ProPlansMRR, proPlansMrrDiff, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisMonth,
|
||||||
|
Period.ThisYear,
|
||||||
|
])
|
||||||
|
|
||||||
|
const plusPlansMrrDiff = await this.revenueModificationRepository.sumMRRDiff({
|
||||||
|
planNames: [SubscriptionName.PlusPlan],
|
||||||
|
billingFrequencies: [SubscriptionBillingFrequency.Annual, SubscriptionBillingFrequency.Monthly],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.statisticsStore.setMeasure(StatisticsMeasure.PlusPlansMRR, plusPlansMrrDiff, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisMonth,
|
||||||
|
Period.ThisYear,
|
||||||
|
])
|
||||||
|
|
||||||
|
return MonthlyRevenue.create(mrrDiff)
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-empty-interface */
|
||||||
|
export interface CalculateMonthlyRecurringRevenueDTO {}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Result } from '../Core/Result'
|
import { Result } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
export interface DomainUseCaseInterface<T> {
|
export interface DomainUseCaseInterface<T> {
|
||||||
execute(...args: any[]): Promise<Result<T>>
|
execute(...args: any[]): Promise<Result<T>>
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ describe('GetUserAnalyticsId', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
analyticsEntity = {
|
analyticsEntity = {
|
||||||
id: 123,
|
id: 123,
|
||||||
userUuid: '1-2-3',
|
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
|
||||||
userEmail: 'test@test.te',
|
username: 'test@test.te',
|
||||||
} as jest.Mocked<AnalyticsEntity>
|
} as jest.Mocked<AnalyticsEntity>
|
||||||
|
|
||||||
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
|
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
|
||||||
@@ -24,11 +24,11 @@ describe('GetUserAnalyticsId', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return analytics id for a user by uuid', async () => {
|
it('should return analytics id for a user by uuid', async () => {
|
||||||
expect(await (await createUseCase().execute({ userUuid: '1-2-3' })).analyticsId).toEqual(123)
|
expect((await createUseCase().execute({ userUuid: '1-2-3' })).analyticsId).toEqual(123)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return analytics id for a user by email', async () => {
|
it('should return analytics id for a user by email', async () => {
|
||||||
expect(await (await createUseCase().execute({ userEmail: 'test@test.te' })).analyticsId).toEqual(123)
|
expect((await createUseCase().execute({ userEmail: 'test@test.te' })).analyticsId).toEqual(123)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error if user is missing analytics entity', async () => {
|
it('should throw error if user is missing analytics entity', async () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { Email } from '../../Common/Email'
|
|
||||||
import { Uuid } from '../../Common/Uuid'
|
|
||||||
import { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
|
import { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
|
||||||
import { UseCaseInterface } from '../UseCaseInterface'
|
import { UseCaseInterface } from '../UseCaseInterface'
|
||||||
import { GetUserAnalyticsIdDTO } from './GetUserAnalyticsIdDTO'
|
import { GetUserAnalyticsIdDTO } from './GetUserAnalyticsIdDTO'
|
||||||
@@ -28,7 +28,7 @@ export class GetUserAnalyticsId implements UseCaseInterface {
|
|||||||
return {
|
return {
|
||||||
analyticsId: analyticsEntity.id,
|
analyticsId: analyticsEntity.id,
|
||||||
userUuid: Uuid.create(analyticsEntity.userUuid).getValue(),
|
userUuid: Uuid.create(analyticsEntity.userUuid).getValue(),
|
||||||
userEmail: Email.create(analyticsEntity.userEmail).getValue(),
|
username: Username.create(analyticsEntity.username).getValue(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -1,8 +1,7 @@
|
|||||||
import { Email } from '../../Common/Email'
|
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||||
import { Uuid } from '../../Common/Uuid'
|
|
||||||
|
|
||||||
export type GetUserAnalyticsIdResponse = {
|
export type GetUserAnalyticsIdResponse = {
|
||||||
analyticsId: number
|
analyticsId: number
|
||||||
userEmail: Email
|
username: Username
|
||||||
userUuid: Uuid
|
userUuid: Uuid
|
||||||
}
|
}
|
||||||
|
|||||||
+180
-11
@@ -1,7 +1,8 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { Email } from '../../Common/Email'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { Uuid } from '../../Common/Uuid'
|
import { Result, Username, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
||||||
|
|
||||||
import { RevenueModification } from '../../Revenue/RevenueModification'
|
import { RevenueModification } from '../../Revenue/RevenueModification'
|
||||||
@@ -9,24 +10,34 @@ import { RevenueModificationRepositoryInterface } from '../../Revenue/RevenueMod
|
|||||||
import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
|
import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
|
||||||
import { SubscriptionPlanName } from '../../Subscription/SubscriptionPlanName'
|
import { SubscriptionPlanName } from '../../Subscription/SubscriptionPlanName'
|
||||||
import { SaveRevenueModification } from './SaveRevenueModification'
|
import { SaveRevenueModification } from './SaveRevenueModification'
|
||||||
|
import { User } from '../../User/User'
|
||||||
|
import { Subscription } from '../../Subscription/Subscription'
|
||||||
|
|
||||||
describe('SaveRevenueModification', () => {
|
describe('SaveRevenueModification', () => {
|
||||||
let revenueModificationRepository: RevenueModificationRepositoryInterface
|
let revenueModificationRepository: RevenueModificationRepositoryInterface
|
||||||
let previousMonthlyRevenue: RevenueModification
|
let previousMonthlyRevenueModification: RevenueModification
|
||||||
|
let timer: TimerInterface
|
||||||
|
|
||||||
const createUseCase = () => new SaveRevenueModification(revenueModificationRepository)
|
const createUseCase = () => new SaveRevenueModification(revenueModificationRepository, timer)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
previousMonthlyRevenue = {
|
const previousMonthlyRevenue = {
|
||||||
newMonthlyRevenue: MonthlyRevenue.create(2).getValue(),
|
value: 2,
|
||||||
|
} as jest.Mocked<MonthlyRevenue>
|
||||||
|
previousMonthlyRevenueModification = {
|
||||||
|
props: {},
|
||||||
} as jest.Mocked<RevenueModification>
|
} as jest.Mocked<RevenueModification>
|
||||||
|
previousMonthlyRevenueModification.props.newMonthlyRevenue = previousMonthlyRevenue
|
||||||
|
|
||||||
revenueModificationRepository = {} as jest.Mocked<RevenueModificationRepositoryInterface>
|
revenueModificationRepository = {} as jest.Mocked<RevenueModificationRepositoryInterface>
|
||||||
revenueModificationRepository.findLastByUserUuid = jest.fn().mockReturnValue(previousMonthlyRevenue)
|
revenueModificationRepository.findLastByUserUuid = jest.fn().mockReturnValue(previousMonthlyRevenueModification)
|
||||||
revenueModificationRepository.save = jest.fn()
|
revenueModificationRepository.save = jest.fn()
|
||||||
|
|
||||||
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
|
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should persist a revenue modification', async () => {
|
it('should persist a revenue modification for subscription purchased event', async () => {
|
||||||
const revenueOrError = await createUseCase().execute({
|
const revenueOrError = await createUseCase().execute({
|
||||||
billingFrequency: 1,
|
billingFrequency: 1,
|
||||||
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
@@ -34,11 +45,169 @@ describe('SaveRevenueModification', () => {
|
|||||||
payedAmount: 12.99,
|
payedAmount: 12.99,
|
||||||
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
subscriptionId: 1234,
|
subscriptionId: 1234,
|
||||||
userEmail: Email.create('test@test.te').getValue(),
|
username: Username.create('test@test.te').getValue(),
|
||||||
userUuid: Uuid.create('1-2-3').getValue(),
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(revenueOrError.isFailed()).toBeFalsy()
|
expect(revenueOrError.isFailed()).toBeFalsy()
|
||||||
const revenue = revenueOrError.getValue()
|
const revenue = revenueOrError.getValue()
|
||||||
expect(revenue.newMonthlyRevenue.value).toEqual(12.99)
|
|
||||||
|
expect(revenue.props.newMonthlyRevenue.value).toEqual(12.99)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should persist a revenue modification for subscription expired event', async () => {
|
||||||
|
const revenueOrError = await createUseCase().execute({
|
||||||
|
billingFrequency: 1,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_EXPIRED').getValue(),
|
||||||
|
newSubscriber: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
subscriptionId: 1234,
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueOrError.isFailed()).toBeFalsy()
|
||||||
|
const revenue = revenueOrError.getValue()
|
||||||
|
|
||||||
|
expect(revenue.props.newMonthlyRevenue.value).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should persist a revenue modification for subscription cancelled event', async () => {
|
||||||
|
const revenueOrError = await createUseCase().execute({
|
||||||
|
billingFrequency: 1,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_CANCELLED').getValue(),
|
||||||
|
newSubscriber: true,
|
||||||
|
payedAmount: 2,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
subscriptionId: 1234,
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueOrError.isFailed()).toBeFalsy()
|
||||||
|
const revenue = revenueOrError.getValue()
|
||||||
|
|
||||||
|
expect(revenue.props.newMonthlyRevenue.value).toEqual(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should persist a revenue modification for subscription purchased event if previous revenue modification did not exist', async () => {
|
||||||
|
revenueModificationRepository.findLastByUserUuid = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
|
const revenueOrError = await createUseCase().execute({
|
||||||
|
billingFrequency: 1,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
|
newSubscriber: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
subscriptionId: 1234,
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueOrError.isFailed()).toBeFalsy()
|
||||||
|
const revenue = revenueOrError.getValue()
|
||||||
|
|
||||||
|
expect(revenue.props.newMonthlyRevenue.value).toEqual(12.99)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not persist a revenue modification if failed to create user', async () => {
|
||||||
|
const mock = jest.spyOn(User, 'create')
|
||||||
|
mock.mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
const revenueOrError = await createUseCase().execute({
|
||||||
|
billingFrequency: 1,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
|
newSubscriber: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
subscriptionId: 1234,
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
mock.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not persist a revenue modification if failed to create a subscription', async () => {
|
||||||
|
const mock = jest.spyOn(Subscription, 'create')
|
||||||
|
mock.mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
const revenueOrError = await createUseCase().execute({
|
||||||
|
billingFrequency: 1,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
|
newSubscriber: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
subscriptionId: 1234,
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
mock.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not persist a revenue modification if failed to create a previous monthly revenue', async () => {
|
||||||
|
const mock = jest.spyOn(MonthlyRevenue, 'create')
|
||||||
|
mock.mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
const revenueOrError = await createUseCase().execute({
|
||||||
|
billingFrequency: 1,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
|
newSubscriber: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
subscriptionId: 1234,
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
mock.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not persist a revenue modification if failed to create a next monthly revenue', async () => {
|
||||||
|
const mock = jest.spyOn(MonthlyRevenue, 'create')
|
||||||
|
mock.mockReturnValueOnce(Result.ok()).mockReturnValueOnce(Result.fail('Oops'))
|
||||||
|
|
||||||
|
const revenueOrError = await createUseCase().execute({
|
||||||
|
billingFrequency: 1,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
|
newSubscriber: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
subscriptionId: 1234,
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
mock.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not persist a revenue modification if failed to create it', async () => {
|
||||||
|
const mock = jest.spyOn(RevenueModification, 'create')
|
||||||
|
mock.mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
const revenueOrError = await createUseCase().execute({
|
||||||
|
billingFrequency: 1,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
|
newSubscriber: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
subscriptionId: 1234,
|
||||||
|
username: Username.create('test@test.te').getValue(),
|
||||||
|
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
mock.mockRestore()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+71
-8
@@ -1,30 +1,38 @@
|
|||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { UniqueEntityId } from '../../Core/UniqueEntityId'
|
|
||||||
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
||||||
import { RevenueModification } from '../../Revenue/RevenueModification'
|
import { RevenueModification } from '../../Revenue/RevenueModification'
|
||||||
import { RevenueModificationRepositoryInterface } from '../../Revenue/RevenueModificationRepositoryInterface'
|
import { RevenueModificationRepositoryInterface } from '../../Revenue/RevenueModificationRepositoryInterface'
|
||||||
import { Subscription } from '../../Subscription/Subscription'
|
import { Subscription } from '../../Subscription/Subscription'
|
||||||
import { User } from '../../User/User'
|
import { User } from '../../User/User'
|
||||||
import { Result } from '../../Core/Result'
|
|
||||||
import { DomainUseCaseInterface } from '../DomainUseCaseInterface'
|
import { DomainUseCaseInterface } from '../DomainUseCaseInterface'
|
||||||
import { SaveRevenueModificationDTO } from './SaveRevenueModificationDTO'
|
import { SaveRevenueModificationDTO } from './SaveRevenueModificationDTO'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SaveRevenueModification implements DomainUseCaseInterface<RevenueModification> {
|
export class SaveRevenueModification implements DomainUseCaseInterface<RevenueModification> {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.RevenueModificationRepository)
|
@inject(TYPES.RevenueModificationRepository)
|
||||||
private revenueModificationRepository: RevenueModificationRepositoryInterface,
|
private revenueModificationRepository: RevenueModificationRepositoryInterface,
|
||||||
|
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: SaveRevenueModificationDTO): Promise<Result<RevenueModification>> {
|
async execute(dto: SaveRevenueModificationDTO): Promise<Result<RevenueModification>> {
|
||||||
const user = User.create(
|
const userOrError = User.create(
|
||||||
{
|
{
|
||||||
email: dto.userEmail,
|
username: dto.username,
|
||||||
},
|
},
|
||||||
new UniqueEntityId(dto.userUuid.value),
|
new UniqueEntityId(dto.userUuid.value),
|
||||||
)
|
)
|
||||||
const subscription = Subscription.create(
|
if (userOrError.isFailed()) {
|
||||||
|
return Result.fail<RevenueModification>(userOrError.getError())
|
||||||
|
}
|
||||||
|
const user = userOrError.getValue()
|
||||||
|
|
||||||
|
const subscriptionOrError = Subscription.create(
|
||||||
{
|
{
|
||||||
isFirstSubscriptionForUser: dto.newSubscriber,
|
isFirstSubscriptionForUser: dto.newSubscriber,
|
||||||
payedAmount: dto.payedAmount,
|
payedAmount: dto.payedAmount,
|
||||||
@@ -33,22 +41,77 @@ export class SaveRevenueModification implements DomainUseCaseInterface<RevenueMo
|
|||||||
},
|
},
|
||||||
new UniqueEntityId(dto.subscriptionId),
|
new UniqueEntityId(dto.subscriptionId),
|
||||||
)
|
)
|
||||||
|
if (subscriptionOrError.isFailed()) {
|
||||||
|
return Result.fail<RevenueModification>(subscriptionOrError.getError())
|
||||||
|
}
|
||||||
|
const subscription = subscriptionOrError.getValue()
|
||||||
|
|
||||||
|
const previousMonthlyRevenueOrError = MonthlyRevenue.create(0)
|
||||||
|
if (previousMonthlyRevenueOrError.isFailed()) {
|
||||||
|
return Result.fail<RevenueModification>(previousMonthlyRevenueOrError.getError())
|
||||||
|
}
|
||||||
|
let previousMonthlyRevenue = previousMonthlyRevenueOrError.getValue()
|
||||||
|
|
||||||
let previousMonthlyRevenue = MonthlyRevenue.create(0).getValue()
|
|
||||||
const previousRevenueModification = await this.revenueModificationRepository.findLastByUserUuid(dto.userUuid)
|
const previousRevenueModification = await this.revenueModificationRepository.findLastByUserUuid(dto.userUuid)
|
||||||
if (previousRevenueModification !== null) {
|
if (previousRevenueModification !== null) {
|
||||||
previousMonthlyRevenue = previousRevenueModification.newMonthlyRevenue
|
previousMonthlyRevenue = previousRevenueModification.props.newMonthlyRevenue
|
||||||
}
|
}
|
||||||
|
const newMonthlyRevenueOrError = this.calculateNewMonthlyRevenue(
|
||||||
|
subscription,
|
||||||
|
previousMonthlyRevenue,
|
||||||
|
dto.eventType,
|
||||||
|
)
|
||||||
|
if (newMonthlyRevenueOrError.isFailed()) {
|
||||||
|
return Result.fail<RevenueModification>(newMonthlyRevenueOrError.getError())
|
||||||
|
}
|
||||||
|
const newMonthlyRevenue = newMonthlyRevenueOrError.getValue()
|
||||||
|
|
||||||
const revenueModification = RevenueModification.create({
|
const revenueModificationOrError = RevenueModification.create({
|
||||||
eventType: dto.eventType,
|
eventType: dto.eventType,
|
||||||
subscription,
|
subscription,
|
||||||
user,
|
user,
|
||||||
previousMonthlyRevenue,
|
previousMonthlyRevenue,
|
||||||
|
newMonthlyRevenue,
|
||||||
|
createdAt: this.timer.getTimestampInMicroseconds(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (revenueModificationOrError.isFailed()) {
|
||||||
|
return Result.fail<RevenueModification>(revenueModificationOrError.getError())
|
||||||
|
}
|
||||||
|
const revenueModification = revenueModificationOrError.getValue()
|
||||||
|
|
||||||
await this.revenueModificationRepository.save(revenueModification)
|
await this.revenueModificationRepository.save(revenueModification)
|
||||||
|
|
||||||
return Result.ok<RevenueModification>(revenueModification)
|
return Result.ok<RevenueModification>(revenueModification)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private calculateNewMonthlyRevenue(
|
||||||
|
subscription: Subscription,
|
||||||
|
previousMonthlyRevenue: MonthlyRevenue,
|
||||||
|
eventType: SubscriptionEventType,
|
||||||
|
): Result<MonthlyRevenue> {
|
||||||
|
let revenue = 0
|
||||||
|
switch (eventType.value) {
|
||||||
|
case 'SUBSCRIPTION_PURCHASED':
|
||||||
|
case 'SUBSCRIPTION_RENEWED':
|
||||||
|
case 'SUBSCRIPTION_DATA_MIGRATED':
|
||||||
|
revenue = subscription.props.payedAmount / subscription.props.billingFrequency
|
||||||
|
break
|
||||||
|
case 'SUBSCRIPTION_EXPIRED':
|
||||||
|
case 'SUBSCRIPTION_REFUNDED':
|
||||||
|
revenue = 0
|
||||||
|
break
|
||||||
|
case 'SUBSCRIPTION_CANCELLED':
|
||||||
|
revenue = previousMonthlyRevenue.value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthlyRevenueOrError = MonthlyRevenue.create(revenue)
|
||||||
|
|
||||||
|
if (monthlyRevenueOrError.isFailed()) {
|
||||||
|
return Result.fail<MonthlyRevenue>(monthlyRevenueOrError.getError())
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok<MonthlyRevenue>(monthlyRevenueOrError.getValue())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user