mirror of
https://github.com/standardnotes/server
synced 2026-01-17 14:04:28 -05:00
Compare commits
12 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ff78452f9 | ||
|
|
9465f2ecd8 | ||
|
|
93c2f1f12f | ||
|
|
ca8a3fc77d | ||
|
|
00936e06bc | ||
|
|
a6dea50d74 | ||
|
|
28b04e6a4a | ||
|
|
d228a86f48 | ||
|
|
0cb234aa47 | ||
|
|
6b554c28b7 | ||
|
|
8a0accd8ea | ||
|
|
d66ae62cf4 |
BIN
.yarn/cache/@aws-sdk-client-s3-npm-3.445.0-fd17753af1-0a75a875cc.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-client-s3-npm-3.445.0-fd17753af1-0a75a875cc.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-client-sso-npm-3.445.0-201a856f2d-e0a8dc55fe.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-client-sso-npm-3.445.0-201a856f2d-e0a8dc55fe.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-client-sts-npm-3.445.0-94a7ac045f-f6d8ba39c0.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-client-sts-npm-3.445.0-94a7ac045f-f6d8ba39c0.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-core-npm-3.445.0-7420fe10a0-49d3719dba.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-core-npm-3.445.0-7420fe10a0-49d3719dba.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-credential-provider-env-npm-3.433.0-c654acd3b0-f1742342e4.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-credential-provider-env-npm-3.433.0-c654acd3b0-f1742342e4.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-credential-provider-ini-npm-3.445.0-ce6e456dae-491bafcbd5.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-credential-provider-ini-npm-3.445.0-ce6e456dae-491bafcbd5.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-credential-provider-node-npm-3.445.0-e46f520013-de0f42a581.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-credential-provider-node-npm-3.445.0-e46f520013-de0f42a581.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-credential-provider-process-npm-3.433.0-1f04178c18-ae5c357a48.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-credential-provider-process-npm-3.433.0-1f04178c18-ae5c357a48.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-credential-provider-sso-npm-3.445.0-55379ab4a6-674ca0b198.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-credential-provider-sso-npm-3.445.0-55379ab4a6-674ca0b198.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-credential-provider-web-identity-npm-3.433.0-d9c6f05357-fd5dc00316.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-credential-provider-web-identity-npm-3.433.0-d9c6f05357-fd5dc00316.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-bucket-endpoint-npm-3.433.0-75f3e083f1-4b8be28542.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-bucket-endpoint-npm-3.433.0-75f3e083f1-4b8be28542.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-expect-continue-npm-3.433.0-686c619b17-5a90a82c97.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-expect-continue-npm-3.433.0-686c619b17-5a90a82c97.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-flexible-checksums-npm-3.433.0-aedc837cff-895134e75f.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-flexible-checksums-npm-3.433.0-aedc837cff-895134e75f.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-host-header-npm-3.433.0-2d83f070b1-9cb0d75e5a.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-host-header-npm-3.433.0-2d83f070b1-9cb0d75e5a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-location-constraint-npm-3.433.0-c994175ec3-b34361ae53.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-location-constraint-npm-3.433.0-c994175ec3-b34361ae53.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-logger-npm-3.433.0-89cf073fb9-0a0e7b0cc7.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-logger-npm-3.433.0-89cf073fb9-0a0e7b0cc7.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-recursion-detection-npm-3.433.0-27e67d3aaa-98d69e0466.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-recursion-detection-npm-3.433.0-27e67d3aaa-98d69e0466.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-sdk-s3-npm-3.440.0-c747652356-445be8121d.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-sdk-s3-npm-3.440.0-c747652356-445be8121d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-sdk-sts-npm-3.433.0-2d279e516c-7c4d1768d3.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-sdk-sts-npm-3.433.0-2d279e516c-7c4d1768d3.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-signing-npm-3.433.0-3c0b9eb352-d7167473ee.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-signing-npm-3.433.0-3c0b9eb352-d7167473ee.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-ssec-npm-3.433.0-975479b90c-c080a66e5a.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-ssec-npm-3.433.0-975479b90c-c080a66e5a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-middleware-user-agent-npm-3.438.0-e2a53e4959-1113259e54.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-middleware-user-agent-npm-3.438.0-e2a53e4959-1113259e54.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-region-config-resolver-npm-3.433.0-bc89f96cf1-450c73dcc7.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-region-config-resolver-npm-3.433.0-bc89f96cf1-450c73dcc7.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-signature-v4-multi-region-npm-3.437.0-38d95938ff-c537a28b20.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-signature-v4-multi-region-npm-3.437.0-38d95938ff-c537a28b20.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-token-providers-npm-3.438.0-4b94ec7c93-9c4f56de35.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-token-providers-npm-3.438.0-4b94ec7c93-9c4f56de35.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-types-npm-3.433.0-a08a292a43-527cda15da.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-types-npm-3.433.0-a08a292a43-527cda15da.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-util-endpoints-npm-3.438.0-6888f0e906-811dc75ece.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-util-endpoints-npm-3.438.0-6888f0e906-811dc75ece.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-util-user-agent-browser-npm-3.433.0-2f197d08d3-7b88a0a8f1.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-util-user-agent-browser-npm-3.433.0-2f197d08d3-7b88a0a8f1.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@aws-sdk-util-user-agent-node-npm-3.437.0-25f3a8fa22-0390fbea8a.zip
vendored
Normal file
BIN
.yarn/cache/@aws-sdk-util-user-agent-node-npm-3.437.0-25f3a8fa22-0390fbea8a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-abort-controller-npm-2.0.12-284507d2d9-ade23e7e6d.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-abort-controller-npm-2.0.12-284507d2d9-ade23e7e6d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-config-resolver-npm-2.0.16-7b859ffe5a-94665f8960.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-config-resolver-npm-2.0.16-7b859ffe5a-94665f8960.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-credential-provider-imds-npm-2.0.18-4843469616-76665cb083.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-credential-provider-imds-npm-2.0.18-4843469616-76665cb083.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-eventstream-codec-npm-2.0.12-ae100efef3-e2be231894.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-eventstream-codec-npm-2.0.12-ae100efef3-e2be231894.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-eventstream-serde-browser-npm-2.0.12-af51c5ffaa-6dbd214f75.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-eventstream-serde-browser-npm-2.0.12-af51c5ffaa-6dbd214f75.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-eventstream-serde-config-resolver-npm-2.0.12-d12ed9f499-869959136f.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-eventstream-serde-config-resolver-npm-2.0.12-d12ed9f499-869959136f.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-eventstream-serde-node-npm-2.0.12-b20645b33e-fb4fab3b5d.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-eventstream-serde-node-npm-2.0.12-b20645b33e-fb4fab3b5d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-eventstream-serde-universal-npm-2.0.12-b1fd809aea-5de9fe4eab.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-eventstream-serde-universal-npm-2.0.12-b1fd809aea-5de9fe4eab.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-fetch-http-handler-npm-2.2.4-a07dc01282-0151a1b7f4.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-fetch-http-handler-npm-2.2.4-a07dc01282-0151a1b7f4.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-hash-blob-browser-npm-2.0.12-8ec60a052a-d0147b2860.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-hash-blob-browser-npm-2.0.12-8ec60a052a-d0147b2860.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-hash-node-npm-2.0.12-dc19684da2-73d8a5121a.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-hash-node-npm-2.0.12-dc19684da2-73d8a5121a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-hash-stream-node-npm-2.0.12-837568259a-bcc24b615b.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-hash-stream-node-npm-2.0.12-837568259a-bcc24b615b.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-invalid-dependency-npm-2.0.12-fc7b4dbf99-4749da004a.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-invalid-dependency-npm-2.0.12-fc7b4dbf99-4749da004a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-md5-js-npm-2.0.12-ff265e11d5-aca6a60871.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-md5-js-npm-2.0.12-ff265e11d5-aca6a60871.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-middleware-content-length-npm-2.0.14-dad35dc8cc-0a2d091368.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-middleware-content-length-npm-2.0.14-dad35dc8cc-0a2d091368.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-middleware-endpoint-npm-2.1.3-ebc2051146-a82b2b1aed.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-middleware-endpoint-npm-2.1.3-ebc2051146-a82b2b1aed.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-middleware-retry-npm-2.0.18-9fe8673f23-e18d073352.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-middleware-retry-npm-2.0.18-9fe8673f23-e18d073352.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-middleware-serde-npm-2.0.12-5c1625ab21-18406d2203.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-middleware-serde-npm-2.0.12-5c1625ab21-18406d2203.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-middleware-stack-npm-2.0.6-fcbeb7e7fc-f98dcd7688.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-middleware-stack-npm-2.0.6-fcbeb7e7fc-f98dcd7688.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-node-config-provider-npm-2.1.3-1ef774bf1f-f53d0cda8c.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-node-config-provider-npm-2.1.3-1ef774bf1f-f53d0cda8c.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-node-http-handler-npm-2.1.8-8ac787c2b2-aca079234e.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-node-http-handler-npm-2.1.8-8ac787c2b2-aca079234e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-property-provider-npm-2.0.13-04595bfd4f-5b9469d17e.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-property-provider-npm-2.0.13-04595bfd4f-5b9469d17e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-protocol-http-npm-3.0.8-3bec477c37-014df5fe50.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-protocol-http-npm-3.0.8-3bec477c37-014df5fe50.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-querystring-builder-npm-2.0.12-1035f75405-e3ba93e719.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-querystring-builder-npm-2.0.12-1035f75405-e3ba93e719.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-querystring-parser-npm-2.0.12-7c25c8b3f7-e491478c97.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-querystring-parser-npm-2.0.12-7c25c8b3f7-e491478c97.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-service-error-classification-npm-2.0.5-e08d76788e-fcd3e267de.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-service-error-classification-npm-2.0.5-e08d76788e-fcd3e267de.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-shared-ini-file-loader-npm-2.2.2-90c60eb131-ee3b6a9b0f.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-shared-ini-file-loader-npm-2.2.2-90c60eb131-ee3b6a9b0f.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-smithy-client-npm-2.1.12-2961adb1b3-78ad4c6599.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-smithy-client-npm-2.1.12-2961adb1b3-78ad4c6599.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-types-npm-2.4.0-79f798587a-d8998f754c.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-types-npm-2.4.0-79f798587a-d8998f754c.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-url-parser-npm-2.0.12-da5f841dc2-636e2548f7.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-url-parser-npm-2.0.12-da5f841dc2-636e2548f7.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-util-defaults-mode-browser-npm-2.0.16-b18594bcc4-388444a93e.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-util-defaults-mode-browser-npm-2.0.16-b18594bcc4-388444a93e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-util-defaults-mode-node-npm-2.0.21-2a982d91e7-f0efa3e351.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-util-defaults-mode-node-npm-2.0.21-2a982d91e7-f0efa3e351.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-util-endpoints-npm-1.0.2-924b9d70eb-7438acafe9.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-util-endpoints-npm-1.0.2-924b9d70eb-7438acafe9.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-util-middleware-npm-2.0.5-bd5abccaea-13dc6d8ee9.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-util-middleware-npm-2.0.5-bd5abccaea-13dc6d8ee9.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-util-retry-npm-2.0.5-4f31bcfd57-83019b0b92.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-util-retry-npm-2.0.5-4f31bcfd57-83019b0b92.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-util-stream-npm-2.0.17-7d45860ae5-3d55c712db.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-util-stream-npm-2.0.17-7d45860ae5-3d55c712db.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@smithy-util-waiter-npm-2.0.12-0d6eb3dd54-bc5a5d1ce2.zip
vendored
Normal file
BIN
.yarn/cache/@smithy-util-waiter-npm-2.0.12-0d6eb3dd54-bc5a5d1ce2.zip
vendored
Normal file
Binary file not shown.
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.32.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.5...@standardnotes/analytics@2.32.6) (2023-11-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.32.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.4...@standardnotes/analytics@2.32.5) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.32.5",
|
||||
"version": "2.32.6",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.81.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.10...@standardnotes/api-gateway@1.81.11) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** remove calling both auth and payments on account deletion request ([6b554c2](https://github.com/standardnotes/api-gateway/commit/6b554c28b731a9080d7ad2942d3fa05c01dcabf2))
|
||||
|
||||
## [1.81.10](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.9...@standardnotes/api-gateway@1.81.10) (2023-11-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.81.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.8...@standardnotes/api-gateway@1.81.9) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.81.9",
|
||||
"version": "1.81.11",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -23,7 +23,6 @@ export class UsersController extends BaseHttpController {
|
||||
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
|
||||
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
|
||||
@inject(TYPES.ApiGateway_Logger) private logger: Logger,
|
||||
@inject(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER) private isConfiguredForHomeServer: boolean,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -238,10 +237,6 @@ export class UsersController extends BaseHttpController {
|
||||
|
||||
@httpDelete('/:userUuid', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
|
||||
async deleteUser(request: Request, response: Response): Promise<void> {
|
||||
if (!this.isConfiguredForHomeServer) {
|
||||
await this.httpService.callPaymentsServer(request, response, 'api/account', request.body, true)
|
||||
}
|
||||
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
|
||||
@@ -151,7 +151,6 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
returnRawResponse?: boolean,
|
||||
): Promise<void | Response<unknown, Record<string, unknown>>> {
|
||||
if (!this.paymentsServerUrl) {
|
||||
this.logger.debug('Payments Server URL not defined. Skipped request to Payments API.')
|
||||
@@ -159,18 +158,13 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
return
|
||||
}
|
||||
|
||||
const rawResponse = await this.callServerWithLegacyFormat(
|
||||
await this.callServerWithLegacyFormat(
|
||||
this.paymentsServerUrl,
|
||||
request,
|
||||
response,
|
||||
endpointOrMethodIdentifier,
|
||||
payload,
|
||||
returnRawResponse,
|
||||
)
|
||||
|
||||
if (returnRawResponse) {
|
||||
return rawResponse
|
||||
}
|
||||
}
|
||||
|
||||
async callAuthServerWithLegacyFormat(
|
||||
@@ -345,7 +339,6 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
returnRawResponse?: boolean,
|
||||
): Promise<void | Response<unknown, Record<string, unknown>>> {
|
||||
const serviceResponse = await this.getServerResponse(
|
||||
serverUrl,
|
||||
@@ -364,18 +357,10 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
if (serviceResponse.request._redirectable._redirectCount > 0) {
|
||||
response.status(302)
|
||||
|
||||
if (returnRawResponse) {
|
||||
return response
|
||||
}
|
||||
|
||||
response.redirect(serviceResponse.request.res.responseUrl)
|
||||
} else {
|
||||
response.status(serviceResponse.status)
|
||||
|
||||
if (returnRawResponse) {
|
||||
return response
|
||||
}
|
||||
|
||||
response.send(serviceResponse.data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ export interface ServiceProxyInterface {
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
returnRawResponse?: boolean,
|
||||
): Promise<void | Response<unknown, Record<string, unknown>>>
|
||||
callWebSocketServer(
|
||||
request: Request,
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.167.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.1...@standardnotes/auth-server@1.167.2) (2023-11-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add logs about sending websocket events to clients ([9465f2e](https://github.com/standardnotes/server/commit/9465f2ecd8e8f0bf3ebeeb3976227b1b105aded0))
|
||||
|
||||
## [1.167.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.0...@standardnotes/auth-server@1.167.1) (2023-11-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** path to delete accounts script ([ca8a3fc](https://github.com/standardnotes/server/commit/ca8a3fc77d91410f0dee8c3ddef29c09947c9cf5))
|
||||
|
||||
# [1.167.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.166.0...@standardnotes/auth-server@1.167.0) (2023-11-08)
|
||||
|
||||
### Features
|
||||
|
||||
* script to mass delete accounts from CSV source ([#913](https://github.com/standardnotes/server/issues/913)) ([a6dea50](https://github.com/standardnotes/server/commit/a6dea50d745ff6f051fd9ede168aef27036159c3))
|
||||
|
||||
# [1.166.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.4...@standardnotes/auth-server@1.166.0) (2023-11-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add triggering post setting update actions ([#905](https://github.com/standardnotes/server/issues/905)) ([d228a86](https://github.com/standardnotes/server/commit/d228a86f48c9ff62b7810244c347abf7770e2b9f))
|
||||
|
||||
## [1.165.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.3...@standardnotes/auth-server@1.165.4) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* account deletion event ([#904](https://github.com/standardnotes/server/issues/904)) ([d66ae62](https://github.com/standardnotes/server/commit/d66ae62cf4f413cac5f6f4eac45dc0f1ddbc9e32))
|
||||
|
||||
## [1.165.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.2...@standardnotes/auth-server@1.165.3) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
|
||||
import { Stream } from 'stream'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
import * as dayjs from 'dayjs'
|
||||
import * as utc from 'dayjs/plugin/utc'
|
||||
@@ -11,78 +7,13 @@ import * as utc from 'dayjs/plugin/utc'
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||
import { MuteFailedBackupsEmailsOption } from '@standardnotes/settings'
|
||||
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { GetUserKeyParams } from '../src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
|
||||
import { TriggerEmailBackupForAllUsers } from '../src/Domain/UseCase/TriggerEmailBackupForAllUsers/TriggerEmailBackupForAllUsers'
|
||||
|
||||
const inputArgs = process.argv.slice(2)
|
||||
const backupProvider = inputArgs[0]
|
||||
const backupFrequency = inputArgs[1]
|
||||
const backupFrequency = inputArgs[0]
|
||||
|
||||
const requestBackups = async (
|
||||
settingRepository: SettingRepositoryInterface,
|
||||
roleService: RoleServiceInterface,
|
||||
domainEventFactory: DomainEventFactoryInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
getUserKeyParamsUseCase: GetUserKeyParams,
|
||||
): Promise<void> => {
|
||||
const settingName = SettingName.create(SettingName.NAMES.EmailBackupFrequency).getValue()
|
||||
const permissionName = PermissionName.DailyEmailBackup
|
||||
const muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
||||
|
||||
const stream = await settingRepository.streamAllByNameAndValue(settingName, backupFrequency)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream
|
||||
.pipe(
|
||||
new Stream.Transform({
|
||||
objectMode: true,
|
||||
transform: async (setting, _encoding, callback) => {
|
||||
const userIsPermittedForEmailBackups = await roleService.userHasPermission(
|
||||
setting.setting_user_uuid,
|
||||
permissionName,
|
||||
)
|
||||
if (!userIsPermittedForEmailBackups) {
|
||||
callback()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let userHasEmailsMuted = false
|
||||
const emailsMutedSetting = await settingRepository.findOneByNameAndUserUuid(
|
||||
muteEmailsSettingName,
|
||||
setting.setting_user_uuid,
|
||||
)
|
||||
if (emailsMutedSetting !== null && emailsMutedSetting.props.value !== null) {
|
||||
userHasEmailsMuted = emailsMutedSetting.props.value === muteEmailsSettingValue
|
||||
}
|
||||
|
||||
const keyParamsResponse = await getUserKeyParamsUseCase.execute({
|
||||
userUuid: setting.setting_user_uuid,
|
||||
authenticated: false,
|
||||
})
|
||||
|
||||
await domainEventPublisher.publish(
|
||||
domainEventFactory.createEmailBackupRequestedEvent(
|
||||
setting.setting_user_uuid,
|
||||
emailsMutedSetting?.id.toString() as string,
|
||||
userHasEmailsMuted,
|
||||
keyParamsResponse.keyParams,
|
||||
),
|
||||
)
|
||||
|
||||
callback()
|
||||
},
|
||||
}),
|
||||
)
|
||||
.on('finish', resolve)
|
||||
.on('error', reject)
|
||||
})
|
||||
const requestBackups = async (triggerEmailBackupForAllUsers: TriggerEmailBackupForAllUsers): Promise<void> => {
|
||||
await triggerEmailBackupForAllUsers.execute({ backupFrequency })
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
@@ -94,24 +25,20 @@ void container.load().then((container) => {
|
||||
|
||||
const logger: Logger = container.get(TYPES.Auth_Logger)
|
||||
|
||||
logger.info(`Starting ${backupFrequency} ${backupProvider} backup requesting...`)
|
||||
logger.info(`Starting ${backupFrequency} email backup requesting...`)
|
||||
|
||||
const settingRepository: SettingRepositoryInterface = container.get(TYPES.Auth_SettingRepository)
|
||||
const roleService: RoleServiceInterface = container.get(TYPES.Auth_RoleService)
|
||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory)
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
|
||||
const getUserKeyParamsUseCase: GetUserKeyParams = container.get(TYPES.Auth_GetUserKeyParams)
|
||||
|
||||
Promise.resolve(
|
||||
requestBackups(settingRepository, roleService, domainEventFactory, domainEventPublisher, getUserKeyParamsUseCase),
|
||||
const triggerEmailBackupForAllUsers: TriggerEmailBackupForAllUsers = container.get(
|
||||
TYPES.Auth_TriggerEmailBackupForAllUsers,
|
||||
)
|
||||
|
||||
Promise.resolve(requestBackups(triggerEmailBackupForAllUsers))
|
||||
.then(() => {
|
||||
logger.info(`${backupFrequency} ${backupProvider} backup requesting complete`)
|
||||
logger.info(`${backupFrequency} email backup requesting complete`)
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not finish ${backupFrequency} ${backupProvider} backup requesting: ${error.message}`)
|
||||
logger.error(`Could not finish ${backupFrequency} email backup requesting: ${error.message}`)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
43
packages/auth/bin/delete_accounts.ts
Normal file
43
packages/auth/bin/delete_accounts.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { DeleteAccountsFromCSVFile } from '../src/Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile'
|
||||
|
||||
const inputArgs = process.argv.slice(2)
|
||||
const fileName = inputArgs[0]
|
||||
const mode = inputArgs[1]
|
||||
|
||||
const deleteAccounts = async (deleteAccountsFromCSVFile: DeleteAccountsFromCSVFile): Promise<void> => {
|
||||
await deleteAccountsFromCSVFile.execute({
|
||||
fileName,
|
||||
dryRun: mode !== 'delete',
|
||||
})
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const logger: Logger = container.get(TYPES.Auth_Logger)
|
||||
|
||||
logger.info('Starting mass accounts deletion from CSV file')
|
||||
|
||||
const deleteAccountsFromCSVFile = container.get<DeleteAccountsFromCSVFile>(TYPES.Auth_DeleteAccountsFromCSVFile)
|
||||
|
||||
Promise.resolve(deleteAccounts(deleteAccountsFromCSVFile))
|
||||
.then(() => {
|
||||
logger.info('Accounts deleted.')
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not delete accounts: ${error.message}`)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
11
packages/auth/docker/entrypoint-delete-accounts.js
Normal file
11
packages/auth/docker/entrypoint-delete-accounts.js
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
|
||||
|
||||
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/delete_accounts.js')))
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
|
||||
exports.default = index
|
||||
@@ -26,12 +26,12 @@ case "$COMMAND" in
|
||||
|
||||
'email-daily-backup' )
|
||||
echo "[Docker] Starting Email Daily Backup..."
|
||||
node docker/entrypoint-backup.js email daily
|
||||
node docker/entrypoint-backup.js daily
|
||||
;;
|
||||
|
||||
'email-weekly-backup' )
|
||||
echo "[Docker] Starting Email Weekly Backup..."
|
||||
node docker/entrypoint-backup.js email weekly
|
||||
node docker/entrypoint-backup.js weekly
|
||||
;;
|
||||
|
||||
'email-backup' )
|
||||
@@ -40,19 +40,11 @@ case "$COMMAND" in
|
||||
node docker/entrypoint-user-email-backup.js $EMAIL
|
||||
;;
|
||||
|
||||
'dropbox-daily-backup' )
|
||||
echo "[Docker] Starting Dropbox Daily Backup..."
|
||||
node docker/entrypoint-backup.js dropbox daily
|
||||
;;
|
||||
|
||||
'google-drive-daily-backup' )
|
||||
echo "[Docker] Starting Google Drive Daily Backup..."
|
||||
node docker/entrypoint-backup.js google_drive daily
|
||||
;;
|
||||
|
||||
'one-drive-daily-backup' )
|
||||
echo "[Docker] Starting One Drive Daily Backup..."
|
||||
node docker/entrypoint-backup.js one_drive daily
|
||||
'delete-accounts' )
|
||||
echo "[Docker] Starting Accounts Deleting from CSV..."
|
||||
FILE_NAME=$1 && shift 1
|
||||
MODE=$1 && shift 1
|
||||
node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.165.3",
|
||||
"version": "1.167.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -24,17 +24,15 @@
|
||||
"worker": "yarn node dist/bin/worker.js",
|
||||
"cleanup": "yarn node dist/bin/cleanup.js",
|
||||
"stats": "yarn node dist/bin/stats.js",
|
||||
"daily-backup:email": "yarn node dist/bin/backup.js email daily",
|
||||
"daily-backup:email": "yarn node dist/bin/backup.js daily",
|
||||
"user-email-backup": "yarn node dist/bin/user_email_backup.js",
|
||||
"daily-backup:dropbox": "yarn node dist/bin/backup.js dropbox daily",
|
||||
"daily-backup:google_drive": "yarn node dist/bin/backup.js google_drive daily",
|
||||
"daily-backup:one_drive": "yarn node dist/bin/backup.js one_drive daily",
|
||||
"weekly-backup:email": "yarn node dist/bin/backup.js email weekly",
|
||||
"weekly-backup:email": "yarn node dist/bin/backup.js weekly",
|
||||
"content-recalculation": "yarn node dist/bin/content.js",
|
||||
"typeorm": "typeorm-ts-node-commonjs",
|
||||
"migrate": "yarn build && yarn typeorm migration:run -d dist/src/Bootstrap/DataSource.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.445.0",
|
||||
"@aws-sdk/client-sns": "^3.427.0",
|
||||
"@aws-sdk/client-sqs": "^3.427.0",
|
||||
"@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as winston from 'winston'
|
||||
import Redis from 'ioredis'
|
||||
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
|
||||
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
|
||||
import { S3Client } from '@aws-sdk/client-s3'
|
||||
import { Container } from 'inversify'
|
||||
import {
|
||||
DomainEventHandlerInterface,
|
||||
@@ -130,8 +131,6 @@ import { ListedAccountCreatedEventHandler } from '../Domain/Handler/ListedAccoun
|
||||
import { ListedAccountDeletedEventHandler } from '../Domain/Handler/ListedAccountDeletedEventHandler'
|
||||
import { FileRemovedEventHandler } from '../Domain/Handler/FileRemovedEventHandler'
|
||||
import { UserDisabledSessionUserAgentLoggingEventHandler } from '../Domain/Handler/UserDisabledSessionUserAgentLoggingEventHandler'
|
||||
import { SettingInterpreterInterface } from '../Domain/Setting/SettingInterpreterInterface'
|
||||
import { SettingInterpreter } from '../Domain/Setting/SettingInterpreter'
|
||||
import { SettingCrypterInterface } from '../Domain/Setting/SettingCrypterInterface'
|
||||
import { SettingCrypter } from '../Domain/Setting/SettingCrypter'
|
||||
import { SharedSubscriptionInvitationRepositoryInterface } from '../Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface'
|
||||
@@ -275,6 +274,12 @@ import { SubscriptionSettingPersistenceMapper } from '../Mapping/Persistence/Sub
|
||||
import { ApplyDefaultSettings } from '../Domain/UseCase/ApplyDefaultSettings/ApplyDefaultSettings'
|
||||
import { AuthResponseFactoryResolverInterface } from '../Domain/Auth/AuthResponseFactoryResolverInterface'
|
||||
import { UserInvitedToSharedVaultEventHandler } from '../Domain/Handler/UserInvitedToSharedVaultEventHandler'
|
||||
import { TriggerPostSettingUpdateActions } from '../Domain/UseCase/TriggerPostSettingUpdateActions/TriggerPostSettingUpdateActions'
|
||||
import { TriggerEmailBackupForUser } from '../Domain/UseCase/TriggerEmailBackupForUser/TriggerEmailBackupForUser'
|
||||
import { TriggerEmailBackupForAllUsers } from '../Domain/UseCase/TriggerEmailBackupForAllUsers/TriggerEmailBackupForAllUsers'
|
||||
import { CSVFileReaderInterface } from '../Domain/CSV/CSVFileReaderInterface'
|
||||
import { S3CsvFileReader } from '../Infra/S3/S3CsvFileReader'
|
||||
import { DeleteAccountsFromCSVFile } from '../Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||
@@ -369,6 +374,19 @@ export class ContainerConfigLoader {
|
||||
}
|
||||
const sqsClient = new SQSClient(sqsConfig)
|
||||
container.bind<SQSClient>(TYPES.Auth_SQS).toConstantValue(sqsClient)
|
||||
|
||||
container.bind<S3Client>(TYPES.Auth_S3).toConstantValue(
|
||||
new S3Client({
|
||||
apiVersion: 'latest',
|
||||
region: env.get('S3_AWS_REGION', true),
|
||||
}),
|
||||
)
|
||||
|
||||
container
|
||||
.bind<CSVFileReaderInterface>(TYPES.Auth_CSVFileReader)
|
||||
.toConstantValue(
|
||||
new S3CsvFileReader(env.get('S3_AUTH_SCRIPTS_DATA_BUCKET', true), container.get<S3Client>(TYPES.Auth_S3)),
|
||||
)
|
||||
}
|
||||
|
||||
container.bind(TYPES.Auth_SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN', true))
|
||||
@@ -772,16 +790,6 @@ export class ContainerConfigLoader {
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SettingInterpreterInterface>(TYPES.Auth_SettingInterpreter)
|
||||
.toConstantValue(
|
||||
new SettingInterpreter(
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
|
||||
container.get<SettingRepositoryInterface>(TYPES.Auth_SettingRepository),
|
||||
container.get<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams),
|
||||
),
|
||||
)
|
||||
|
||||
container.bind<OfflineSettingServiceInterface>(TYPES.Auth_OfflineSettingService).to(OfflineSettingService)
|
||||
container.bind<ContentDecoderInterface>(TYPES.Auth_ContenDecoder).toConstantValue(new ContentDecoder())
|
||||
@@ -791,7 +799,16 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<SubscriptionSettingsAssociationServiceInterface>(TYPES.Auth_SubscriptionSettingsAssociationService)
|
||||
.to(SubscriptionSettingsAssociationService)
|
||||
container.bind<FeatureServiceInterface>(TYPES.Auth_FeatureService).to(FeatureService)
|
||||
container
|
||||
.bind<FeatureServiceInterface>(TYPES.Auth_FeatureService)
|
||||
.toConstantValue(
|
||||
new FeatureService(
|
||||
container.get<RoleToSubscriptionMapInterface>(TYPES.Auth_RoleToSubscriptionMap),
|
||||
container.get<OfflineUserSubscriptionRepositoryInterface>(TYPES.Auth_OfflineUserSubscriptionRepository),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SelectorInterface<boolean>>(TYPES.Auth_BooleanSelector)
|
||||
.toConstantValue(new DeterministicSelector<boolean>())
|
||||
@@ -1104,6 +1121,7 @@ export class ContainerConfigLoader {
|
||||
new DeleteAccount(
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
|
||||
container.get<GetSharedSubscriptionForUser>(TYPES.Auth_GetSharedSubscriptionForUser),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
|
||||
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||
@@ -1221,6 +1239,46 @@ export class ContainerConfigLoader {
|
||||
container.get<GetSharedOrRegularSubscriptionForUser>(TYPES.Auth_GetSharedOrRegularSubscriptionForUser),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<TriggerEmailBackupForUser>(TYPES.Auth_TriggerEmailBackupForUser)
|
||||
.toConstantValue(
|
||||
new TriggerEmailBackupForUser(
|
||||
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<TriggerEmailBackupForAllUsers>(TYPES.Auth_TriggerEmailBackupForAllUsers)
|
||||
.toConstantValue(
|
||||
new TriggerEmailBackupForAllUsers(
|
||||
container.get<SettingRepositoryInterface>(TYPES.Auth_SettingRepository),
|
||||
container.get<TriggerEmailBackupForUser>(TYPES.Auth_TriggerEmailBackupForUser),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<TriggerPostSettingUpdateActions>(TYPES.Auth_TriggerPostSettingUpdateActions)
|
||||
.toConstantValue(
|
||||
new TriggerPostSettingUpdateActions(
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
|
||||
container.get<TriggerEmailBackupForUser>(TYPES.Auth_TriggerEmailBackupForUser),
|
||||
),
|
||||
)
|
||||
if (!isConfiguredForHomeServer) {
|
||||
container
|
||||
.bind<DeleteAccountsFromCSVFile>(TYPES.Auth_DeleteAccountsFromCSVFile)
|
||||
.toConstantValue(
|
||||
new DeleteAccountsFromCSVFile(
|
||||
container.get<CSVFileReaderInterface>(TYPES.Auth_CSVFileReader),
|
||||
container.get<DeleteAccount>(TYPES.Auth_DeleteAccount),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Controller
|
||||
container
|
||||
@@ -1645,11 +1703,13 @@ export class ContainerConfigLoader {
|
||||
container.get<GetAllSettingsForUser>(TYPES.Auth_GetAllSettingsForUser),
|
||||
container.get<GetSetting>(TYPES.Auth_GetSetting),
|
||||
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
|
||||
container.get<TriggerPostSettingUpdateActions>(TYPES.Auth_TriggerPostSettingUpdateActions),
|
||||
container.get<DeleteSetting>(TYPES.Auth_DeleteSetting),
|
||||
container.get<MapperInterface<Setting, SettingHttpRepresentation>>(TYPES.Auth_SettingHttpMapper),
|
||||
container.get<MapperInterface<SubscriptionSetting, SubscriptionSettingHttpRepresentation>>(
|
||||
TYPES.Auth_SubscriptionSettingHttpMapper,
|
||||
),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ const TYPES = {
|
||||
Auth_Redis: Symbol.for('Auth_Redis'),
|
||||
Auth_SNS: Symbol.for('Auth_SNS'),
|
||||
Auth_SQS: Symbol.for('Auth_SQS'),
|
||||
Auth_S3: Symbol.for('Auth_S3'),
|
||||
// Mapping
|
||||
Auth_SessionTracePersistenceMapper: Symbol.for('Auth_SessionTracePersistenceMapper'),
|
||||
Auth_AuthenticatorChallengePersistenceMapper: Symbol.for('Auth_AuthenticatorChallengePersistenceMapper'),
|
||||
@@ -164,6 +165,10 @@ const TYPES = {
|
||||
Auth_DesignateSurvivor: Symbol.for('Auth_DesignateSurvivor'),
|
||||
Auth_GetSharedOrRegularSubscriptionForUser: Symbol.for('Auth_GetSharedOrRegularSubscriptionForUser'),
|
||||
Auth_DisableEmailSettingBasedOnEmailSubscription: Symbol.for('Auth_DisableEmailSettingBasedOnEmailSubscription'),
|
||||
Auth_TriggerPostSettingUpdateActions: Symbol.for('Auth_TriggerPostSettingUpdateActions'),
|
||||
Auth_TriggerEmailBackupForUser: Symbol.for('Auth_TriggerEmailBackupForUser'),
|
||||
Auth_TriggerEmailBackupForAllUsers: Symbol.for('Auth_TriggerEmailBackupForAllUsers'),
|
||||
Auth_DeleteAccountsFromCSVFile: Symbol.for('Auth_DeleteAccountsFromCSVFile'),
|
||||
// Handlers
|
||||
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
|
||||
Auth_SubscriptionPurchasedEventHandler: Symbol.for('Auth_SubscriptionPurchasedEventHandler'),
|
||||
@@ -230,7 +235,6 @@ const TYPES = {
|
||||
Auth_SubscriptionSettingsAssociationService: Symbol.for('Auth_SubscriptionSettingsAssociationService'),
|
||||
Auth_FeatureService: Symbol.for('Auth_FeatureService'),
|
||||
Auth_SettingCrypter: Symbol.for('Auth_SettingCrypter'),
|
||||
Auth_SettingInterpreter: Symbol.for('Auth_SettingInterpreter'),
|
||||
Auth_ProtocolVersionSelector: Symbol.for('Auth_ProtocolVersionSelector'),
|
||||
Auth_BooleanSelector: Symbol.for('Auth_BooleanSelector'),
|
||||
Auth_BaseAuthController: Symbol.for('Auth_BaseAuthController'),
|
||||
@@ -249,6 +253,7 @@ const TYPES = {
|
||||
Auth_BaseOfflineController: Symbol.for('Auth_BaseOfflineController'),
|
||||
Auth_BaseListedController: Symbol.for('Auth_BaseListedController'),
|
||||
Auth_BaseFeaturesController: Symbol.for('Auth_BaseFeaturesController'),
|
||||
Auth_CSVFileReader: Symbol.for('Auth_CSVFileReader'),
|
||||
}
|
||||
|
||||
export default TYPES
|
||||
|
||||
5
packages/auth/src/Domain/CSV/CSVFileReaderInterface.ts
Normal file
5
packages/auth/src/Domain/CSV/CSVFileReaderInterface.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
export interface CSVFileReaderInterface {
|
||||
getValues(fileName: string): Promise<Result<string[]>>
|
||||
}
|
||||
@@ -281,9 +281,16 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
|
||||
createAccountDeletionRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
email: string
|
||||
userCreatedAtTimestamp: number
|
||||
regularSubscriptionUuid: string | undefined
|
||||
roleNames: string[]
|
||||
regularSubscription?: {
|
||||
uuid: string
|
||||
ownerUuid: string
|
||||
}
|
||||
sharedSubscription?: {
|
||||
uuid: string
|
||||
ownerUuid: string
|
||||
}
|
||||
}): AccountDeletionRequestedEvent {
|
||||
return {
|
||||
type: 'ACCOUNT_DELETION_REQUESTED',
|
||||
|
||||
@@ -45,9 +45,16 @@ export interface DomainEventFactoryInterface {
|
||||
): EmailBackupRequestedEvent
|
||||
createAccountDeletionRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
email: string
|
||||
userCreatedAtTimestamp: number
|
||||
regularSubscriptionUuid: string | undefined
|
||||
roleNames: string[]
|
||||
regularSubscription?: {
|
||||
uuid: string
|
||||
ownerUuid: string
|
||||
}
|
||||
sharedSubscription?: {
|
||||
uuid: string
|
||||
ownerUuid: string
|
||||
}
|
||||
}): AccountDeletionRequestedEvent
|
||||
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: string[]): UserRolesChangedEvent
|
||||
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
|
||||
|
||||
@@ -35,6 +35,7 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
|
||||
describe('FeatureService', () => {
|
||||
let roleToSubscriptionMap: RoleToSubscriptionMapInterface
|
||||
@@ -52,8 +53,10 @@ describe('FeatureService', () => {
|
||||
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let offlineUserSubscription: OfflineUserSubscription
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
|
||||
const createService = () => new FeatureService(roleToSubscriptionMap, offlineUserSubscriptionRepository, timer)
|
||||
const createService = () =>
|
||||
new FeatureService(roleToSubscriptionMap, offlineUserSubscriptionRepository, timer, userSubscriptionRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
roleToSubscriptionMap = {} as jest.Mocked<RoleToSubscriptionMapInterface>
|
||||
@@ -107,7 +110,7 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.PlusPlan,
|
||||
endsAt: 555,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: false,
|
||||
subscriptionId: 1,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
@@ -120,7 +123,7 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.ProPlan,
|
||||
endsAt: 777,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: false,
|
||||
subscriptionId: 2,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
@@ -133,7 +136,7 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.PlusPlan,
|
||||
endsAt: 333,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: true,
|
||||
subscriptionId: 3,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
@@ -146,7 +149,7 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.PlusPlan,
|
||||
endsAt: 333,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: true,
|
||||
subscriptionId: 4,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
@@ -155,9 +158,11 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve([subscription1]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1])
|
||||
|
||||
offlineUserSubscription = {
|
||||
roles: Promise.resolve([role1]),
|
||||
uuid: 'subscription-1-1-1',
|
||||
@@ -247,9 +252,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2, nonSubscriptionRole]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
expect(await createService().userIsEntitledToFeature(user, 'files-beta')).toBe(true)
|
||||
})
|
||||
|
||||
@@ -269,9 +275,12 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve([subscription3, subscription1, subscription4]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValue([subscription3, subscription1, subscription4])
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
expect(features).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -284,14 +293,13 @@ describe('FeatureService', () => {
|
||||
})
|
||||
|
||||
it('should not return user features if a subscription could not be found', async () => {
|
||||
const subscriptions: Array<UserSubscription> = []
|
||||
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve(subscriptions),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([])
|
||||
|
||||
expect(await createService().getFeaturesForUser(user)).toEqual([])
|
||||
})
|
||||
|
||||
@@ -307,9 +315,12 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve([subscription3, subscription1, subscription4]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValue([subscription3, subscription1, subscription4])
|
||||
|
||||
expect(await createService().getFeaturesForUser(user)).toEqual([])
|
||||
})
|
||||
|
||||
@@ -321,7 +332,7 @@ describe('FeatureService', () => {
|
||||
renewedAt: null,
|
||||
planName: 'non existing plan name' as SubscriptionName,
|
||||
endsAt: 555,
|
||||
user: Promise.resolve(user),
|
||||
userUuid: 'user-1-1-1',
|
||||
cancelled: false,
|
||||
subscriptionId: 1,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
@@ -330,9 +341,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1]),
|
||||
subscriptions: Promise.resolve([subscription1]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1])
|
||||
|
||||
expect(await createService().getFeaturesForUser(user)).toEqual([])
|
||||
})
|
||||
|
||||
@@ -351,9 +363,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
expect(features).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -409,9 +422,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2, nonSubscriptionRole]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
expect(features).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -445,9 +459,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
const longestExpireAt = 777
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
@@ -482,9 +497,10 @@ describe('FeatureService', () => {
|
||||
user = {
|
||||
uuid: 'user-1-1-1',
|
||||
roles: Promise.resolve([role1, role2]),
|
||||
subscriptions: Promise.resolve([subscription1, subscription2]),
|
||||
} as jest.Mocked<User>
|
||||
|
||||
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
|
||||
|
||||
const features = await createService().getFeaturesForUser(user)
|
||||
expect(features).toEqual(
|
||||
expect.arrayContaining([
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { FeatureDescription, GetFeatures } from '@standardnotes/features'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { RoleToSubscriptionMapInterface } from '../Role/RoleToSubscriptionMapInterface'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { RoleToSubscriptionMapInterface } from '../Role/RoleToSubscriptionMapInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { FeatureServiceInterface } from './FeatureServiceInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { Role } from '../Role/Role'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
|
||||
@injectable()
|
||||
export class FeatureService implements FeatureServiceInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_RoleToSubscriptionMap) private roleToSubscriptionMap: RoleToSubscriptionMapInterface,
|
||||
@inject(TYPES.Auth_OfflineUserSubscriptionRepository)
|
||||
private roleToSubscriptionMap: RoleToSubscriptionMapInterface,
|
||||
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
||||
private timer: TimerInterface,
|
||||
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async userIsEntitledToFeature(user: User, featureIdentifier: string): Promise<boolean> {
|
||||
@@ -61,7 +59,7 @@ export class FeatureService implements FeatureServiceInterface {
|
||||
}
|
||||
|
||||
async getFeaturesForUser(user: User): Promise<Array<FeatureDescription>> {
|
||||
const userSubscriptions = await user.subscriptions
|
||||
const userSubscriptions = await this.userSubscriptionRepository.findByUserUuid(user.uuid)
|
||||
|
||||
return this.getFeaturesForSubscriptions(userSubscriptions, await user.roles)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -48,7 +48,22 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
||||
private async removeRoleFromSubscriptionUsers(subscriptionId: number, subscriptionName: string): Promise<void> {
|
||||
const userSubscriptions = await this.userSubscriptionRepository.findBySubscriptionId(subscriptionId)
|
||||
for (const userSubscription of userSubscriptions) {
|
||||
await this.roleService.removeUserRoleBasedOnSubscription(await userSubscription.user, subscriptionName)
|
||||
const userUuidOrError = Uuid.create(userSubscription.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.warn(`Could not remove role from user with uuid: ${userUuidOrError.getError()}`)
|
||||
|
||||
continue
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
if (user === null) {
|
||||
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
await this.roleService.removeUserRoleBasedOnSubscription(user, subscriptionName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
): Promise<UserSubscription> {
|
||||
const subscription = new UserSubscription()
|
||||
subscription.planName = subscriptionName
|
||||
subscription.user = Promise.resolve(user)
|
||||
subscription.userUuid = user.uuid
|
||||
subscription.createdAt = timestamp
|
||||
subscription.updatedAt = timestamp
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
|
||||
@@ -80,7 +80,7 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
|
||||
): Promise<UserSubscription> {
|
||||
const subscription = new UserSubscription()
|
||||
subscription.planName = subscriptionName
|
||||
subscription.user = Promise.resolve(user)
|
||||
subscription.userUuid = user.uuid
|
||||
subscription.createdAt = timestamp
|
||||
subscription.updatedAt = timestamp
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
|
||||
@@ -7,7 +7,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionRefundedEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -48,7 +48,22 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
private async removeRoleFromSubscriptionUsers(subscriptionId: number, subscriptionName: string): Promise<void> {
|
||||
const userSubscriptions = await this.userSubscriptionRepository.findBySubscriptionId(subscriptionId)
|
||||
for (const userSubscription of userSubscriptions) {
|
||||
await this.roleService.removeUserRoleBasedOnSubscription(await userSubscription.user, subscriptionName)
|
||||
const userUuidOrError = Uuid.create(userSubscription.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.warn(`Could not remove role from user with uuid: ${userUuidOrError.getError()}`)
|
||||
|
||||
continue
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
if (user === null) {
|
||||
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
await this.roleService.removeUserRoleBasedOnSubscription(user, subscriptionName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { Logger } from 'winston'
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -71,7 +71,20 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
||||
private async addRoleToSubscriptionUsers(subscriptionId: number, subscriptionName: string): Promise<void> {
|
||||
const userSubscriptions = await this.userSubscriptionRepository.findBySubscriptionId(subscriptionId)
|
||||
for (const userSubscription of userSubscriptions) {
|
||||
const user = await userSubscription.user
|
||||
const userUuidOrError = Uuid.create(userSubscription.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.warn(`Could not add role to user with uuid: ${userUuidOrError.getError()}`)
|
||||
|
||||
continue
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
if (user === null) {
|
||||
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
await this.roleService.addUserRoleBasedOnSubscription(user, subscriptionName)
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
|
||||
}
|
||||
|
||||
subscription.planName = subscriptionName
|
||||
subscription.user = Promise.resolve(user)
|
||||
subscription.userUuid = user.uuid
|
||||
subscription.createdAt = timestamp
|
||||
subscription.updatedAt = timestamp
|
||||
subscription.endsAt = subscriptionExpiresAt
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
import {
|
||||
DomainEventPublisherInterface,
|
||||
EmailBackupRequestedEvent,
|
||||
MuteEmailsSettingChangedEvent,
|
||||
UserDisabledSessionUserAgentLoggingEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { EmailBackupFrequency, LogSessionUserAgentOption, MuteMarketingEmailsOption } from '@standardnotes/settings'
|
||||
import 'reflect-metadata'
|
||||
import { Logger } from 'winston'
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { Setting } from './Setting'
|
||||
import { SettingCrypterInterface } from './SettingCrypterInterface'
|
||||
|
||||
import { SettingInterpreter } from './SettingInterpreter'
|
||||
import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
||||
import { GetUserKeyParams } from '../UseCase/GetUserKeyParams/GetUserKeyParams'
|
||||
import { KeyParamsData } from '@standardnotes/responses'
|
||||
import { Uuid, Timestamps, UniqueEntityId, SettingName } from '@standardnotes/domain-core'
|
||||
|
||||
describe('SettingInterpreter', () => {
|
||||
let user: User
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let settingRepository: SettingRepositoryInterface
|
||||
let settingCrypter: SettingCrypterInterface
|
||||
let logger: Logger
|
||||
let getUserKeyParams: GetUserKeyParams
|
||||
|
||||
const createInterpreter = () =>
|
||||
new SettingInterpreter(domainEventPublisher, domainEventFactory, settingRepository, getUserKeyParams)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {
|
||||
uuid: '4-5-6',
|
||||
email: 'test@test.te',
|
||||
} as jest.Mocked<User>
|
||||
|
||||
settingRepository = {} as jest.Mocked<SettingRepositoryInterface>
|
||||
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
settingCrypter = {} as jest.Mocked<SettingCrypterInterface>
|
||||
settingCrypter.decryptSettingValue = jest.fn().mockReturnValue('decrypted')
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createEmailBackupRequestedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<EmailBackupRequestedEvent>)
|
||||
domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>)
|
||||
domainEventFactory.createMuteEmailsSettingChangedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<MuteEmailsSettingChangedEvent>)
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
logger.warn = jest.fn()
|
||||
logger.error = jest.fn()
|
||||
|
||||
getUserKeyParams = {} as jest.Mocked<GetUserKeyParams>
|
||||
getUserKeyParams.execute = jest.fn().mockReturnValue({ keyParams: {} as jest.Mocked<KeyParamsData> })
|
||||
})
|
||||
|
||||
it('should trigger session cleanup if user is disabling session user agent logging', async () => {
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.LogSessionUserAgent,
|
||||
user,
|
||||
LogSessionUserAgentOption.Disabled,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({
|
||||
userUuid: '4-5-6',
|
||||
email: 'test@test.te',
|
||||
})
|
||||
})
|
||||
|
||||
it('should trigger backup if email backup setting is created - emails not muted', async () => {
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
user,
|
||||
EmailBackupFrequency.Daily,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '', false, {})
|
||||
})
|
||||
|
||||
it('should trigger backup if email backup setting is created - emails muted', async () => {
|
||||
const setting = Setting.create(
|
||||
{
|
||||
name: SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
value: 'muted',
|
||||
serverEncryptionVersion: 0,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sensitive: false,
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId('7fb54003-1dd2-40bd-8900-2bacd6cf629c'),
|
||||
).getValue()
|
||||
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(setting)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
user,
|
||||
EmailBackupFrequency.Daily,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith(
|
||||
'4-5-6',
|
||||
'7fb54003-1dd2-40bd-8900-2bacd6cf629c',
|
||||
true,
|
||||
{},
|
||||
)
|
||||
})
|
||||
|
||||
it('should not trigger backup if email backup setting is disabled', async () => {
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.EmailBackupFrequency,
|
||||
user,
|
||||
EmailBackupFrequency.Disabled,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailBackupRequestedEvent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should trigger mute subscription emails rejection if mute setting changed', async () => {
|
||||
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createInterpreter().interpretSettingUpdated(
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
user,
|
||||
MuteMarketingEmailsOption.Muted,
|
||||
)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createMuteEmailsSettingChangedEvent).toHaveBeenCalledWith({
|
||||
emailSubscriptionRejectionLevel: 'MARKETING',
|
||||
mute: true,
|
||||
username: 'test@test.te',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,113 +0,0 @@
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { EmailLevel, SettingName } from '@standardnotes/domain-core'
|
||||
import { EmailBackupFrequency, LogSessionUserAgentOption, MuteFailedBackupsEmailsOption } from '@standardnotes/settings'
|
||||
|
||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { SettingInterpreterInterface } from './SettingInterpreterInterface'
|
||||
import { SettingRepositoryInterface } from './SettingRepositoryInterface'
|
||||
import { GetUserKeyParams } from '../UseCase/GetUserKeyParams/GetUserKeyParams'
|
||||
|
||||
export class SettingInterpreter implements SettingInterpreterInterface {
|
||||
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<string, string> = new Map([
|
||||
[SettingName.NAMES.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
|
||||
[SettingName.NAMES.MuteFailedCloudBackupsEmails, EmailLevel.LEVELS.FailedCloudBackup],
|
||||
[SettingName.NAMES.MuteMarketingEmails, EmailLevel.LEVELS.Marketing],
|
||||
[SettingName.NAMES.MuteSignInEmails, EmailLevel.LEVELS.SignIn],
|
||||
])
|
||||
|
||||
constructor(
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private settingRepository: SettingRepositoryInterface,
|
||||
private getUserKeyParams: GetUserKeyParams,
|
||||
) {}
|
||||
|
||||
async interpretSettingUpdated(
|
||||
updatedSettingName: string,
|
||||
user: User,
|
||||
unencryptedValue: string | null,
|
||||
): Promise<void> {
|
||||
if (this.isChangingMuteEmailsSetting(updatedSettingName)) {
|
||||
await this.triggerEmailSubscriptionChange(user, updatedSettingName, unencryptedValue)
|
||||
}
|
||||
|
||||
if (this.isEnablingEmailBackupSetting(updatedSettingName, unencryptedValue)) {
|
||||
await this.triggerEmailBackup(user.uuid)
|
||||
}
|
||||
|
||||
if (this.isDisablingSessionUserAgentLogging(updatedSettingName, unencryptedValue)) {
|
||||
await this.triggerSessionUserAgentCleanup(user)
|
||||
}
|
||||
}
|
||||
|
||||
private async triggerEmailBackup(userUuid: string): Promise<void> {
|
||||
let userHasEmailsMuted = false
|
||||
let muteEmailsSettingUuid = ''
|
||||
const muteFailedEmailsBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
|
||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
userUuid,
|
||||
)
|
||||
if (muteFailedEmailsBackupSetting !== null) {
|
||||
userHasEmailsMuted = muteFailedEmailsBackupSetting.props.value === MuteFailedBackupsEmailsOption.Muted
|
||||
muteEmailsSettingUuid = muteFailedEmailsBackupSetting.id.toString()
|
||||
}
|
||||
|
||||
const keyParamsResponse = await this.getUserKeyParams.execute({
|
||||
authenticated: false,
|
||||
userUuid,
|
||||
})
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createEmailBackupRequestedEvent(
|
||||
userUuid,
|
||||
muteEmailsSettingUuid,
|
||||
userHasEmailsMuted,
|
||||
keyParamsResponse.keyParams,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private isChangingMuteEmailsSetting(settingName: string): boolean {
|
||||
return [
|
||||
SettingName.NAMES.MuteFailedBackupsEmails,
|
||||
SettingName.NAMES.MuteFailedCloudBackupsEmails,
|
||||
SettingName.NAMES.MuteMarketingEmails,
|
||||
SettingName.NAMES.MuteSignInEmails,
|
||||
].includes(settingName)
|
||||
}
|
||||
|
||||
private isEnablingEmailBackupSetting(settingName: string, newValue: string | null): boolean {
|
||||
return (
|
||||
settingName === SettingName.NAMES.EmailBackupFrequency &&
|
||||
[EmailBackupFrequency.Daily, EmailBackupFrequency.Weekly].includes(newValue as EmailBackupFrequency)
|
||||
)
|
||||
}
|
||||
|
||||
private isDisablingSessionUserAgentLogging(settingName: string, newValue: string | null): boolean {
|
||||
return SettingName.NAMES.LogSessionUserAgent === settingName && LogSessionUserAgentOption.Disabled === newValue
|
||||
}
|
||||
|
||||
private async triggerEmailSubscriptionChange(
|
||||
user: User,
|
||||
settingName: string,
|
||||
unencryptedValue: string | null,
|
||||
): Promise<void> {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createMuteEmailsSettingChangedEvent({
|
||||
username: user.email,
|
||||
mute: unencryptedValue === 'muted',
|
||||
emailSubscriptionRejectionLevel: this.emailSettingToSubscriptionRejectionLevelMap.get(settingName) as string,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
private async triggerSessionUserAgentCleanup(user: User) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent({
|
||||
userUuid: user.uuid,
|
||||
email: user.email,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { User } from '../User/User'
|
||||
|
||||
export interface SettingInterpreterInterface {
|
||||
interpretSettingUpdated(updatedSettingName: string, user: User, newUnencryptedValue: string | null): Promise<void>
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ReadStream } from 'fs'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
|
||||
import { DeleteSettingDto } from '../UseCase/DeleteSetting/DeleteSettingDto'
|
||||
@@ -10,8 +9,8 @@ export interface SettingRepositoryInterface {
|
||||
findOneByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
||||
findLastByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
|
||||
findAllByUserUuid(userUuid: string): Promise<Setting[]>
|
||||
streamAllByNameAndValue(name: SettingName, value: string): Promise<ReadStream>
|
||||
streamAllByName(name: SettingName): Promise<ReadStream>
|
||||
countAllByNameAndValue(dto: { name: SettingName; value: string }): Promise<number>
|
||||
findAllByNameAndValue(dto: { name: SettingName; value: string; offset: number; limit: number }): Promise<Setting[]>
|
||||
deleteByUserUuid(dto: DeleteSettingDto): Promise<void>
|
||||
insert(setting: Setting): Promise<void>
|
||||
update(setting: Setting): Promise<void>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { User } from '../User/User'
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { UserSubscriptionType } from './UserSubscriptionType'
|
||||
|
||||
@Entity({ name: 'user_subscriptions' })
|
||||
@@ -63,14 +62,9 @@ export class UserSubscription {
|
||||
})
|
||||
declare subscriptionType: UserSubscriptionType
|
||||
|
||||
@ManyToOne(
|
||||
/* istanbul ignore next */
|
||||
() => User,
|
||||
/* istanbul ignore next */
|
||||
(user) => user.subscriptions,
|
||||
/* istanbul ignore next */
|
||||
{ onDelete: 'CASCADE', nullable: false, lazy: true, eager: false },
|
||||
)
|
||||
@JoinColumn({ name: 'user_uuid', referencedColumnName: 'uuid' })
|
||||
declare user: Promise<User>
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare userUuid: string
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('AcceptSharedSubscriptionInvitation', () => {
|
||||
subscriptionId: 3,
|
||||
subscriptionType: 'shared',
|
||||
updatedAt: 1,
|
||||
user: Promise.resolve(invitee),
|
||||
userUuid: '123',
|
||||
})
|
||||
expect(roleService.addUserRoleBasedOnSubscription).toHaveBeenCalledWith(invitee, 'PLUS_PLAN')
|
||||
expect(applyDefaultSubscriptionSettings.execute).toHaveBeenCalled()
|
||||
@@ -145,7 +145,7 @@ describe('AcceptSharedSubscriptionInvitation', () => {
|
||||
subscriptionId: 3,
|
||||
subscriptionType: 'shared',
|
||||
updatedAt: 3,
|
||||
user: Promise.resolve(invitee),
|
||||
userUuid: '123',
|
||||
})
|
||||
expect(roleService.addUserRoleBasedOnSubscription).toHaveBeenCalledWith(invitee, 'PLUS_PLAN')
|
||||
expect(applyDefaultSubscriptionSettings.execute).toHaveBeenCalled()
|
||||
|
||||
@@ -115,7 +115,7 @@ export class AcceptSharedSubscriptionInvitation implements UseCaseInterface {
|
||||
): Promise<UserSubscription> {
|
||||
const subscription = new UserSubscription()
|
||||
subscription.planName = subscriptionName
|
||||
subscription.user = Promise.resolve(user)
|
||||
subscription.userUuid = user.uuid
|
||||
const timestamp = this.timer.getTimestampInMicroseconds()
|
||||
subscription.createdAt = timestamp
|
||||
subscription.updatedAt = timestamp
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user