Compare commits

..

16 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
161 changed files with 2534 additions and 865 deletions
Generated
+904 -2
View File
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.
+6
View File
@@ -3,6 +3,12 @@
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
+6
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()
})
+2 -2
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
;;
* )
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.32.6",
"version": "2.33.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
+24
View File
@@ -3,6 +3,30 @@
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
+11 -2
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}`)
})
+1 -1
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
;;
* )
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.81.11",
"version": "1.82.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -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(
@@ -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
+24
View File
@@ -3,6 +3,30 @@
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
+43
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)
})
})
+7 -2
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}`)
})
+6
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()
})
@@ -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
+14 -7
View File
@@ -6,38 +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 daily
exec node docker/entrypoint-backup.js daily
;;
'email-weekly-backup' )
echo "[Docker] Starting Email Weekly Backup..."
node docker/entrypoint-backup.js 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
;;
'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
;;
* )
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.166.0",
"version": "1.168.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -32,6 +32,7 @@
"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",
+28
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,
@@ -276,6 +277,9 @@ import { UserInvitedToSharedVaultEventHandler } from '../Domain/Handler/UserInvi
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') {}
@@ -370,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))
@@ -1251,6 +1268,17 @@ export class ContainerConfigLoader {
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
+3
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'),
@@ -167,6 +168,7 @@ const TYPES = {
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'),
@@ -251,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
@@ -0,0 +1,5 @@
import { Result } from '@standardnotes/domain-core'
export interface CSVFileReaderInterface {
getValues(fileName: string): Promise<Result<string[]>>
}
@@ -0,0 +1,72 @@
import { Logger } from 'winston'
import { Result } from '@standardnotes/domain-core'
import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface'
import { DeleteAccount } from '../DeleteAccount/DeleteAccount'
import { DeleteAccountsFromCSVFile } from './DeleteAccountsFromCSVFile'
describe('DeleteAccountsFromCSVFile', () => {
let csvFileReader: CSVFileReaderInterface
let deleteAccount: DeleteAccount
let logger: Logger
const createUseCase = () => new DeleteAccountsFromCSVFile(csvFileReader, deleteAccount, logger)
beforeEach(() => {
csvFileReader = {} as jest.Mocked<CSVFileReaderInterface>
csvFileReader.getValues = jest.fn().mockResolvedValue(Result.ok(['email1']))
deleteAccount = {} as jest.Mocked<DeleteAccount>
deleteAccount.execute = jest.fn().mockResolvedValue(Result.ok(''))
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
})
it('should delete accounts', async () => {
const useCase = createUseCase()
const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
expect(result.isFailed()).toBeFalsy()
})
it('should return error if csv file is invalid', async () => {
csvFileReader.getValues = jest.fn().mockResolvedValue(Result.fail('Oops'))
const useCase = createUseCase()
const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
expect(result.isFailed()).toBeTruthy()
})
it('should return error if csv file is empty', async () => {
csvFileReader.getValues = jest.fn().mockResolvedValue(Result.ok([]))
const useCase = createUseCase()
const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
expect(result.isFailed()).toBeTruthy()
})
it('should do nothing on a dry run', async () => {
const useCase = createUseCase()
const result = await useCase.execute({ fileName: 'test.csv', dryRun: true })
expect(deleteAccount.execute).not.toHaveBeenCalled()
expect(result.isFailed()).toBeFalsy()
})
it('should return error if delete account fails', async () => {
deleteAccount.execute = jest.fn().mockResolvedValue(Result.fail('Oops'))
const useCase = createUseCase()
const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
expect(result.isFailed()).toBeTruthy()
})
})
@@ -0,0 +1,47 @@
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { DeleteAccount } from '../DeleteAccount/DeleteAccount'
import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface'
import { DeleteAccountsFromCSVFileDTO } from './DeleteAccountsFromCSVFileDTO'
export class DeleteAccountsFromCSVFile implements UseCaseInterface<void> {
constructor(
private csvFileReader: CSVFileReaderInterface,
private deleteAccount: DeleteAccount,
private logger: Logger,
) {}
async execute(dto: DeleteAccountsFromCSVFileDTO): Promise<Result<void>> {
const emailsOrError = await this.csvFileReader.getValues(dto.fileName)
if (emailsOrError.isFailed()) {
return Result.fail(emailsOrError.getError())
}
const emails = emailsOrError.getValue()
if (emails.length === 0) {
return Result.fail(`No emails found in CSV file ${dto.fileName}`)
}
if (dto.dryRun) {
const firstTenEmails = emails.slice(0, 10)
this.logger.info(
`Dry run mode enabled. Would delete ${emails.length} accounts. First 10 emails: ${firstTenEmails}`,
)
return Result.ok()
}
for (const email of emails) {
const deleteAccountOrError = await this.deleteAccount.execute({
username: email,
})
if (deleteAccountOrError.isFailed()) {
return Result.fail(deleteAccountOrError.getError())
}
}
return Result.ok()
}
}
@@ -0,0 +1,4 @@
export interface DeleteAccountsFromCSVFileDTO {
fileName: string
dryRun: boolean
}
@@ -0,0 +1,32 @@
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
import { Result } from '@standardnotes/domain-core'
import { CSVFileReaderInterface } from '../../Domain/CSV/CSVFileReaderInterface'
export class S3CsvFileReader implements CSVFileReaderInterface {
constructor(
private s3BucketName: string,
private s3Client: S3Client,
) {}
async getValues(fileName: string): Promise<Result<string[]>> {
if (this.s3BucketName.length === 0) {
return Result.fail('S3 bucket name is not set')
}
const response = await this.s3Client.send(
new GetObjectCommand({
Bucket: this.s3BucketName,
Key: fileName,
}),
)
if (response.Body === undefined) {
return Result.fail(`Could not find CSV file at path: ${fileName}`)
}
const csvContent = await response.Body.transformToString()
return Result.ok(csvContent.split('\n').filter((line) => line.length > 0))
}
}
@@ -5,12 +5,14 @@ import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFacto
import { User } from '../../Domain/User/User'
import { ClientServiceInterface } from '../../Domain/Client/ClientServiceInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { Logger } from 'winston'
@injectable()
export class WebSocketsClientService implements ClientServiceInterface {
constructor(
@inject(TYPES.Auth_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.Auth_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.Auth_Logger) private logger: Logger,
) {}
async sendUserRolesChangedEvent(user: User): Promise<void> {
@@ -20,6 +22,8 @@ export class WebSocketsClientService implements ClientServiceInterface {
(await user.roles).map((role) => role.name),
)
this.logger.info(`[WebSockets] Requesting message ${event.type} to user ${user.uuid}`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createWebSocketMessageRequestedEvent({
userUuid: user.uuid,
@@ -3,4 +3,4 @@
set -euo pipefail
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
@@ -3,4 +3,4 @@
set -euo pipefail
sh supervisor/wait-for.sh localhost $AUTH_SERVER_PORT
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.20.4...@standardnotes/domain-events-infra@1.21.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.20.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.20.3...@standardnotes/domain-events-infra@1.20.4) (2023-11-07)
**Note:** Version bump only for package @standardnotes/domain-events-infra
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.20.4",
"version": "1.21.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -1,23 +0,0 @@
import 'reflect-metadata'
import * as IORedis from 'ioredis'
import { RedisDomainEventSubscriber } from './RedisDomainEventSubscriber'
describe('RedisDomainEventSubscriber', () => {
let redisClient: IORedis.Redis
const eventChannel = 'test-channel'
const createSubscriber = () => new RedisDomainEventSubscriber(redisClient, eventChannel)
beforeEach(() => {
redisClient = {} as jest.Mocked<IORedis.Redis>
redisClient.subscribe = jest.fn()
})
it('should start the subscription', () => {
createSubscriber().start()
expect(redisClient.subscribe).toHaveBeenCalledWith('test-channel')
})
})
@@ -1,14 +0,0 @@
import * as IORedis from 'ioredis'
import { DomainEventSubscriberInterface } from '@standardnotes/domain-events'
export class RedisDomainEventSubscriber implements DomainEventSubscriberInterface {
constructor(
private redisClient: IORedis.Redis,
private eventChannel: string,
) {}
start(): void {
void this.redisClient.subscribe(this.eventChannel)
}
}
@@ -1,31 +0,0 @@
import 'reflect-metadata'
import * as IORedis from 'ioredis'
import { RedisDomainEventSubscriberFactory } from './RedisDomainEventSubscriberFactory'
import { DomainEventMessageHandlerInterface } from '@standardnotes/domain-events'
import { RedisDomainEventSubscriber } from './RedisDomainEventSubscriber'
describe('RedisDomainEventSubscriberFactory', () => {
let redisClient: IORedis.Redis
let domainEventMessageHandler: DomainEventMessageHandlerInterface
const eventChannel = 'events'
const createFactory = () =>
new RedisDomainEventSubscriberFactory(redisClient, domainEventMessageHandler, eventChannel)
beforeEach(() => {
redisClient = {} as jest.Mocked<IORedis.Redis>
redisClient.on = jest.fn()
domainEventMessageHandler = {} as jest.Mocked<DomainEventMessageHandlerInterface>
domainEventMessageHandler.handleMessage = jest.fn()
})
it('should create an event subscriber', () => {
const subscriber = createFactory().create()
expect(subscriber).toBeInstanceOf(RedisDomainEventSubscriber)
expect(redisClient.on).toHaveBeenCalledWith('message', expect.any(Function))
})
})
@@ -1,29 +0,0 @@
import * as IORedis from 'ioredis'
import {
DomainEventSubscriberFactoryInterface,
DomainEventSubscriberInterface,
DomainEventMessageHandlerInterface,
} from '@standardnotes/domain-events'
import { RedisDomainEventSubscriber } from './RedisDomainEventSubscriber'
export class RedisDomainEventSubscriberFactory implements DomainEventSubscriberFactoryInterface {
constructor(
private redisClient: IORedis.Redis,
private domainEventMessageHandler: DomainEventMessageHandlerInterface,
private eventChannel: string,
) {}
create(): DomainEventSubscriberInterface {
const subscriber = new RedisDomainEventSubscriber(this.redisClient, this.eventChannel)
this.redisClient.on(
'message',
/* istanbul ignore next */
async (_channel: string, message: string) => await this.domainEventMessageHandler.handleMessage(message),
)
return subscriber
}
}

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