Compare commits

..

22 Commits

Author SHA1 Message Date
standardci
e6d8e5c5f2 chore(release): publish new version
- @standardnotes/analytics@2.33.0
 - @standardnotes/api-gateway@1.82.0
 - @standardnotes/auth-server@1.168.0
 - @standardnotes/domain-events-infra@1.21.0
 - @standardnotes/domain-events@2.134.0
 - @standardnotes/event-store@1.14.0
 - @standardnotes/files-server@1.33.0
 - @standardnotes/home-server@1.19.0
 - @standardnotes/revisions-server@1.48.0
 - @standardnotes/scheduler-server@1.27.0
 - @standardnotes/syncing-server@1.121.0
 - @standardnotes/websockets-server@1.18.0
2023-11-10 11:46:24 +00:00
Karol Sójko
c24353cc24 feat: add graceful shutdown procedures upon SIGTERM (#923) 2023-11-10 12:20:21 +01:00
standardci
4855e1d5f5 chore(release): publish new version
- @standardnotes/api-gateway@1.81.14
 - @standardnotes/home-server@1.18.32
2023-11-10 10:04:31 +00:00
Karol Sójko
5d3fb9a537 fix(api-gateway): add logs about calling web sockets with minimal format 2023-11-10 10:32:47 +01:00
standardci
b55d80a7cd chore(release): publish new version
- @standardnotes/api-gateway@1.81.13
 - @standardnotes/home-server@1.18.31
2023-11-09 14:29:28 +00:00
Karol Sójko
16f92bdc99 fix(api-gateway): add possibility to configure keep-alive timeout (#920) 2023-11-09 15:02:32 +01:00
standardci
4c5738416a chore(release): publish new version
- @standardnotes/home-server@1.18.30
 - @standardnotes/syncing-server@1.120.5
 - @standardnotes/websockets-server@1.17.8
2023-11-09 13:26:30 +00:00
Karol Sójko
45d4920e0f fix: remove unused axios dep in subservices 2023-11-09 14:03:44 +01:00
standardci
94e738532a chore(release): publish new version
- @standardnotes/api-gateway@1.81.12
 - @standardnotes/home-server@1.18.29
 - @standardnotes/websockets-server@1.17.7
2023-11-09 11:18:35 +00:00
Karol Sójko
c4ae12d53f fix: reduce websockets api communication data (#919) 2023-11-09 11:44:31 +01:00
standardci
4ff78452f9 chore(release): publish new version
- @standardnotes/auth-server@1.167.2
 - @standardnotes/home-server@1.18.28
 - @standardnotes/syncing-server@1.120.4
2023-11-08 15:38:47 +00:00
Karol Sójko
9465f2ecd8 fix: add logs about sending websocket events to clients 2023-11-08 16:10:44 +01:00
standardci
93c2f1f12f chore(release): publish new version
- @standardnotes/auth-server@1.167.1
 - @standardnotes/home-server@1.18.27
2023-11-08 12:15:35 +00:00
Karol Sójko
ca8a3fc77d fix(auth): path to delete accounts script 2023-11-08 12:30:12 +01:00
standardci
00936e06bc chore(release): publish new version
- @standardnotes/auth-server@1.167.0
 - @standardnotes/home-server@1.18.26
2023-11-08 10:48:51 +00:00
Karol Sójko
a6dea50d74 feat: script to mass delete accounts from CSV source (#913) 2023-11-08 11:21:00 +01:00
standardci
28b04e6a4a chore(release): publish new version
- @standardnotes/auth-server@1.166.0
 - @standardnotes/home-server@1.18.25
2023-11-07 14:49:54 +00:00
Karol Sójko
d228a86f48 feat(auth): add triggering post setting update actions (#905)
* feat(auth): add triggering post setting update actions

* feat(auth): refactor email backups

* fix: add extra logs for backups

* fix: specs
2023-11-07 15:14:51 +01:00
standardci
0cb234aa47 chore(release): publish new version
- @standardnotes/api-gateway@1.81.11
 - @standardnotes/home-server@1.18.24
2023-11-07 11:28:36 +00:00
Karol Sójko
6b554c28b7 fix(api-gateway): remove calling both auth and payments on account deletion request 2023-11-07 12:02:40 +01:00
standardci
8a0accd8ea chore(release): publish new version
- @standardnotes/analytics@2.32.6
 - @standardnotes/api-gateway@1.81.10
 - @standardnotes/auth-server@1.165.4
 - @standardnotes/domain-events-infra@1.20.4
 - @standardnotes/domain-events@2.133.1
 - @standardnotes/event-store@1.13.19
 - @standardnotes/files-server@1.32.5
 - @standardnotes/home-server@1.18.23
 - @standardnotes/revisions-server@1.47.5
 - @standardnotes/scheduler-server@1.26.6
 - @standardnotes/syncing-server@1.120.3
 - @standardnotes/websockets-server@1.17.6
2023-11-07 11:00:09 +00:00
Karol Sójko
d66ae62cf4 fix: account deletion event (#904)
* fix: account deletion event

* fix: feature service binding
2023-11-07 11:34:10 +01:00
209 changed files with 3418 additions and 1455 deletions

906
.pnp.cjs generated

File diff suppressed because it is too large Load Diff

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.

View File

@@ -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.
# [2.33.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.6...@standardnotes/analytics@2.33.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [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

View File

@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})

View File

@@ -6,12 +6,12 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-worker' )
echo "[Docker] Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
'report' )
echo "[Docker] Starting Usage Report Generation..."
node docker/entrypoint-report.js
exec node docker/entrypoint-report.js
;;
* )

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.32.5",
"version": "2.33.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.82.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.14...@standardnotes/api-gateway@1.82.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/api-gateway/issues/923)) ([c24353c](https://github.com/standardnotes/api-gateway/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.81.14](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.13...@standardnotes/api-gateway@1.81.14) (2023-11-10)
### Bug Fixes
* **api-gateway:** add logs about calling web sockets with minimal format ([5d3fb9a](https://github.com/standardnotes/api-gateway/commit/5d3fb9a537f6971cfe8ae3c5ea449806cc4de8a0))
## [1.81.13](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.12...@standardnotes/api-gateway@1.81.13) (2023-11-09)
### Bug Fixes
* **api-gateway:** add possibility to configure keep-alive timeout ([#920](https://github.com/standardnotes/api-gateway/issues/920)) ([16f92bd](https://github.com/standardnotes/api-gateway/commit/16f92bdc990ded5c3f1fe5af1e6e4a113a9954de))
## [1.81.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.11...@standardnotes/api-gateway@1.81.12) (2023-11-09)
### Bug Fixes
* reduce websockets api communication data ([#919](https://github.com/standardnotes/api-gateway/issues/919)) ([c4ae12d](https://github.com/standardnotes/api-gateway/commit/c4ae12d53fc166879f90a4c5dbad1ab1cb4797e2))
## [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

View File

@@ -102,9 +102,18 @@ void container.load().then((container) => {
})
})
const serverInstance = server.build()
const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT'))
const keepAliveTimeout = env.get('KEEP_ALIVE_TIMEOUT', true) ? +env.get('KEEP_ALIVE_TIMEOUT', true) : 5000
serverInstance.keepAliveTimeout = keepAliveTimeout
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${process.env.PORT}`)
})

View File

@@ -6,7 +6,7 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "Starting Web..."
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
;;
* )

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.81.9",
"version": "1.82.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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,

View File

@@ -143,7 +143,21 @@ export class HttpServiceProxy implements ServiceProxyInterface {
return
}
await this.callServer(this.webSocketServerUrl, request, response, endpointOrMethodIdentifier, payload)
const isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat = request.headers.connectionid !== undefined
this.logger.info(
`Calling websockets service: ${endpointOrMethodIdentifier}. Format is minimal: ${isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat}`,
)
if (isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat) {
await this.callServerWithLegacyFormat(
this.webSocketServerUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
} else {
await this.callServer(this.webSocketServerUrl, request, response, endpointOrMethodIdentifier, payload)
}
}
async callPaymentsServer(
@@ -151,7 +165,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 +172,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 +353,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 +371,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)
}
}

View File

@@ -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,

View File

@@ -6,4 +6,4 @@ sh supervisor/wait-for.sh localhost $AUTH_SERVER_PORT
sh supervisor/wait-for.sh localhost $FILES_SERVER_PORT
sh supervisor/wait-for.sh localhost $REVISIONS_SERVER_PORT
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js

View File

@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.168.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.2...@standardnotes/auth-server@1.168.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [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

View File

@@ -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)
})

View 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)
})
})

View File

@@ -64,9 +64,14 @@ void container.load().then((container) => {
})
})
const serverInstance = server.build()
const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT'))
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${process.env.PORT}`)
})

View File

@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.Auth_DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})

View 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

View File

@@ -6,53 +6,45 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "[Docker] Starting Web..."
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
;;
'start-worker' )
echo "[Docker] Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
'cleanup' )
echo "[Docker] Starting Cleanup..."
node docker/entrypoint-cleanup.js
exec node docker/entrypoint-cleanup.js
;;
'stats' )
echo "[Docker] Starting Persisting Stats..."
node docker/entrypoint-stats.js
exec node docker/entrypoint-stats.js
;;
'email-daily-backup' )
echo "[Docker] Starting Email Daily Backup..."
node docker/entrypoint-backup.js email daily
exec node docker/entrypoint-backup.js daily
;;
'email-weekly-backup' )
echo "[Docker] Starting Email Weekly Backup..."
node docker/entrypoint-backup.js email weekly
exec node docker/entrypoint-backup.js weekly
;;
'email-backup' )
echo "[Docker] Starting Email Backup For Single User..."
EMAIL=$1 && shift 1
node docker/entrypoint-user-email-backup.js $EMAIL
exec 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
exec node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
;;
* )

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.165.3",
"version": "1.168.0",
"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",

View File

@@ -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),
),
)

View File

@@ -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

View File

@@ -0,0 +1,5 @@
import { Result } from '@standardnotes/domain-core'
export interface CSVFileReaderInterface {
getValues(fileName: string): Promise<Result<string[]>>
}

View File

@@ -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',

View File

@@ -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

View File

@@ -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([

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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

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