Compare commits

..

8 Commits

Author SHA1 Message Date
standardci ac26024990 chore(release): publish new version
- @standardnotes/auth-server@1.16.1
2022-08-10 09:21:31 +00:00
Karol Sójko f67be9d3ba fix(auth): docker entrypoing command for single user backup 2022-08-10 11:19:53 +02:00
standardci bb85e3ef11 chore(release): publish new version
- @standardnotes/auth-server@1.16.0
2022-08-10 09:19:42 +00:00
Karol Sójko edd254531d feat(auth): add triggering email backup for a single user 2022-08-10 11:18:01 +02:00
standardci 0f1ff97a60 chore(release): publish new version
- @standardnotes/analytics@1.14.0
 - @standardnotes/api-gateway@1.11.4
 - @standardnotes/auth-server@1.15.0
 - @standardnotes/syncing-server@1.6.41
2022-08-09 18:49:11 +00:00
Karol Sójko 24e439f017 feat(auth): add subscription events to analytics 2022-08-09 20:47:29 +02:00
standardci 0a01fd58eb chore(release): publish new version
- @standardnotes/analytics@1.13.0
 - @standardnotes/api-gateway@1.11.3
 - @standardnotes/auth-server@1.14.0
 - @standardnotes/syncing-server@1.6.40
2022-08-09 18:20:44 +00:00
Karol Sójko f25195b2c1 feat(auth): track registration in analytics 2022-08-09 20:18:53 +02:00
17 changed files with 255 additions and 12 deletions
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.13.0...@standardnotes/analytics@1.14.0) (2022-08-09)
### Features
* **auth:** add subscription events to analytics ([24e439f](https://github.com/standardnotes/server/commit/24e439f017df23d0158940848c10e0b3398720b2))
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.12.2...@standardnotes/analytics@1.13.0) (2022-08-09)
### Features
* **auth:** track registration in analytics ([f25195b](https://github.com/standardnotes/server/commit/f25195b2c156fa03ca5806ef568c4195da7b688a))
## [1.12.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.12.1...@standardnotes/analytics@1.12.2) (2022-08-09)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "1.12.2",
"version": "1.14.0",
"engines": {
"node": ">=14.0.0 <17.0.0"
},
@@ -2,6 +2,9 @@ export enum AnalyticsActivity {
GeneralActivity = 'general-activity',
EditingItems = 'editing-items',
Login = 'login',
Register = 'register',
SubscriptionPurchased = 'subscription-purchased',
SubscriptionRenewed = 'subscription-renewed',
EmailUnbackedUpData = 'email-unbacked-up-data',
EmailBackup = 'email-backup',
LimitedDiscountOfferPurchased = 'limited-discount-offer-purchased',
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.3...@standardnotes/api-gateway@1.11.4) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.11.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.2...@standardnotes/api-gateway@1.11.3) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.11.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.1...@standardnotes/api-gateway@1.11.2) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.11.2",
"version": "1.11.4",
"engines": {
"node": ">=16.0.0 <17.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.16.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.16.0...@standardnotes/auth-server@1.16.1) (2022-08-10)
### Bug Fixes
* **auth:** docker entrypoing command for single user backup ([f67be9d](https://github.com/standardnotes/server/commit/f67be9d3ba56f67ad63cb067c6304766de3f3b69))
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.15.0...@standardnotes/auth-server@1.16.0) (2022-08-10)
### Features
* **auth:** add triggering email backup for a single user ([edd2545](https://github.com/standardnotes/server/commit/edd254531ddff6726aa4395193e489023358e422))
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.14.0...@standardnotes/auth-server@1.15.0) (2022-08-09)
### Features
* **auth:** add subscription events to analytics ([24e439f](https://github.com/standardnotes/server/commit/24e439f017df23d0158940848c10e0b3398720b2))
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.6...@standardnotes/auth-server@1.14.0) (2022-08-09)
### Features
* **auth:** track registration in analytics ([f25195b](https://github.com/standardnotes/server/commit/f25195b2c156fa03ca5806ef568c4195da7b688a))
## [1.13.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.5...@standardnotes/auth-server@1.13.6) (2022-08-09)
**Note:** Version bump only for package @standardnotes/auth-server
+123
View File
@@ -0,0 +1,123 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
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, SettingName } from '@standardnotes/settings'
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
import { PermissionName } from '@standardnotes/features'
import { AnalyticsEntityRepositoryInterface } from '../src/Domain/Analytics/AnalyticsEntityRepositoryInterface'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
const inputArgs = process.argv.slice(2)
const backupEmail = inputArgs[0]
const requestBackups = async (
userRepository: UserRepositoryInterface,
settingRepository: SettingRepositoryInterface,
roleService: RoleServiceInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
analyticsEntityRepository: AnalyticsEntityRepositoryInterface,
analyticsStore: AnalyticsStoreInterface,
): Promise<void> => {
const permissionName = PermissionName.DailyEmailBackup
const muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
if (!backupEmail) {
throw new Error('Could not trigger email backup for user - missing email parameter')
}
const user = await userRepository.findOneByEmail(backupEmail)
if (user === null) {
throw new Error(`Could not find user with email: ${backupEmail}`)
}
const userIsPermittedForEmailBackups = await roleService.userHasPermission(user.uuid, permissionName)
if (!userIsPermittedForEmailBackups) {
throw new Error(`User ${backupEmail} is not permitted for email backups`)
}
let userHasEmailsMuted = false
const emailsMutedSetting = await settingRepository.findOneByNameAndUserUuid(muteEmailsSettingName, user.uuid)
if (emailsMutedSetting !== null && emailsMutedSetting.value !== null) {
userHasEmailsMuted = emailsMutedSetting.value === muteEmailsSettingValue
}
const analyticsEntity = await analyticsEntityRepository.findOneByUserUuid(user.uuid)
if (analyticsEntity === null) {
return
}
await domainEventPublisher.publish(
domainEventFactory.createEmailBackupRequestedEvent(
user.uuid,
emailsMutedSetting?.uuid as string,
userHasEmailsMuted,
),
)
await analyticsStore.markActivity([AnalyticsActivity.EmailBackup], analyticsEntity.id, [
Period.Today,
Period.ThisWeek,
])
await analyticsStore.unmarkActivity([AnalyticsActivity.EmailUnbackedUpData], analyticsEntity.id, [
Period.Today,
Period.ThisWeek,
])
return
}
const container = new ContainerConfigLoader()
void container.load().then((container) => {
dayjs.extend(utc)
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info(`Starting email backup requesting for ${backupEmail} ...`)
const settingRepository: SettingRepositoryInterface = container.get(TYPES.SettingRepository)
const userRepository: UserRepositoryInterface = container.get(TYPES.UserRepository)
const roleService: RoleServiceInterface = container.get(TYPES.RoleService)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
const analyticsEntityRepository: AnalyticsEntityRepositoryInterface = container.get(TYPES.AnalyticsEntityRepository)
const analyticsStore: AnalyticsStoreInterface = container.get(TYPES.AnalyticsStore)
Promise.resolve(
requestBackups(
userRepository,
settingRepository,
roleService,
domainEventFactory,
domainEventPublisher,
analyticsEntityRepository,
analyticsStore,
),
)
.then(() => {
logger.info(`Email backup requesting complete for ${backupEmail}`)
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish email backup requesting for ${backupEmail}: ${error.message}`)
process.exit(1)
})
})
+6
View File
@@ -29,6 +29,12 @@ case "$COMMAND" in
yarn workspace @standardnotes/auth-server weekly-backup:email
;;
'email-backup' )
echo "Starting Email Backup For Single User..."
EMAIL=$1 && shift 1
yarn workspace @standardnotes/auth-server user-email-backup $EMAIL
;;
'dropbox-daily-backup' )
echo "Starting Dropbox Daily Backup..."
yarn workspace @standardnotes/auth-server daily-backup:dropbox
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.13.6",
"version": "1.16.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -21,6 +21,7 @@
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js",
"daily-backup:email": "yarn node dist/bin/backup.js email 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",
@@ -18,6 +18,7 @@ import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSett
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
describe('SubscriptionPurchasedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -31,6 +32,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
let event: SubscriptionPurchasedEvent
let subscriptionExpiresAt: number
let subscriptionSettingService: SubscriptionSettingServiceInterface
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
let timestamp: number
@@ -41,6 +43,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
offlineUserSubscriptionRepository,
roleService,
subscriptionSettingService,
getUserAnalyticsId,
analyticsStore,
logger,
)
@@ -93,6 +96,9 @@ describe('SubscriptionPurchasedEventHandler', () => {
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription = jest.fn()
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
@@ -14,6 +14,7 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
@@ -24,6 +25,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
@inject(TYPES.RoleService) private roleService: RoleServiceInterface,
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -65,14 +67,14 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
event.payload.subscriptionName,
)
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [Period.Today])
const limitedDiscountPurchased = event.payload.discountCode === 'limited-10'
if (limitedDiscountPurchased) {
const analyticsEntity = await user.analyticsEntity
if (analyticsEntity) {
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsEntity.id, [
Period.Today,
])
}
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsId, [
Period.Today,
])
}
}
@@ -13,6 +13,8 @@ import { UserSubscription } from '../Subscription/UserSubscription'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
describe('SubscriptionRenewedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -26,6 +28,8 @@ describe('SubscriptionRenewedEventHandler', () => {
let event: SubscriptionRenewedEvent
let subscriptionExpiresAt: number
let timestamp: number
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
const createHandler = () =>
new SubscriptionRenewedEventHandler(
@@ -33,6 +37,8 @@ describe('SubscriptionRenewedEventHandler', () => {
userSubscriptionRepository,
offlineUserSubscriptionRepository,
roleService,
getUserAnalyticsId,
analyticsStore,
logger,
)
@@ -83,6 +89,12 @@ describe('SubscriptionRenewedEventHandler', () => {
offline: false,
}
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.warn = jest.fn()
})
@@ -1,5 +1,6 @@
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import TYPES from '../../Bootstrap/Types'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
@@ -9,6 +10,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { Logger } from 'winston'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
@@ -18,6 +20,8 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
@inject(TYPES.OfflineUserSubscriptionRepository)
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
@inject(TYPES.RoleService) private roleService: RoleServiceInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -55,6 +59,9 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
}
await this.addRoleToSubscriptionUsers(event.payload.subscriptionId, event.payload.subscriptionName)
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [Period.Today])
}
private async addRoleToSubscriptionUsers(subscriptionId: number, subscriptionName: SubscriptionName): Promise<void> {
@@ -4,16 +4,27 @@ import { Logger } from 'winston'
import { UserRegisteredEventHandler } from './UserRegisteredEventHandler'
import { AxiosInstance } from 'axios'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
describe('UserRegisteredEventHandler', () => {
let httpClient: AxiosInstance
const userServerRegistrationUrl = 'https://user-server/registration'
const userServerAuthKey = 'auth-key'
let event: UserRegisteredEvent
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
let logger: Logger
const createHandler = () =>
new UserRegisteredEventHandler(httpClient, userServerRegistrationUrl, userServerAuthKey, logger)
new UserRegisteredEventHandler(
httpClient,
userServerRegistrationUrl,
userServerAuthKey,
getUserAnalyticsId,
analyticsStore,
logger,
)
beforeEach(() => {
httpClient = {} as jest.Mocked<AxiosInstance>
@@ -26,6 +37,12 @@ describe('UserRegisteredEventHandler', () => {
email: 'test@test.te',
}
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
})
@@ -52,7 +69,14 @@ describe('UserRegisteredEventHandler', () => {
})
it('should not send a request to the user management server about a registration if url is not defined', async () => {
const handler = new UserRegisteredEventHandler(httpClient, '', userServerAuthKey, logger)
const handler = new UserRegisteredEventHandler(
httpClient,
'',
userServerAuthKey,
getUserAnalyticsId,
analyticsStore,
logger,
)
await handler.handle(event)
expect(httpClient.request).not.toHaveBeenCalled()
@@ -1,9 +1,11 @@
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { DomainEventHandlerInterface, UserRegisteredEvent } from '@standardnotes/domain-events'
import { AxiosInstance } from 'axios'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
@@ -11,6 +13,8 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.USER_SERVER_REGISTRATION_URL) private userServerRegistrationUrl: string,
@inject(TYPES.USER_SERVER_AUTH_KEY) private userServerAuthKey: string,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -20,6 +24,9 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
return
}
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsId, [Period.Today])
await this.httpClient.request({
method: 'POST',
url: this.userServerRegistrationUrl,
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.41](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.40...@standardnotes/syncing-server@1.6.41) (2022-08-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.40](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.39...@standardnotes/syncing-server@1.6.40) (2022-08-09)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.39](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.38...@standardnotes/syncing-server@1.6.39) (2022-08-09)
**Note:** Version bump only for package @standardnotes/syncing-server
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.6.39",
"version": "1.6.41",
"engines": {
"node": ">=16.0.0 <17.0.0"
},