Explorar o código

feat(syncing-server): add extended revisions frequency for free users (#965)

Karol Sójko hai 1 ano
pai
achega
398c10ce4b

+ 3 - 0
packages/api-gateway/src/Controller/GRPCWebSocketAuthMiddleware.ts

@@ -5,6 +5,7 @@ import { BaseMiddleware } from 'inversify-express-utils'
 import { verify } from 'jsonwebtoken'
 import { verify } from 'jsonwebtoken'
 import { Logger } from 'winston'
 import { Logger } from 'winston'
 import { ConnectionValidationResponse, IAuthClient, WebsocketConnectionAuthorizationHeader } from '@standardnotes/grpc'
 import { ConnectionValidationResponse, IAuthClient, WebsocketConnectionAuthorizationHeader } from '@standardnotes/grpc'
+import { RoleName } from '@standardnotes/domain-core'
 
 
 export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
 export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
   constructor(
   constructor(
@@ -96,6 +97,8 @@ export class GRPCWebSocketAuthMiddleware extends BaseMiddleware {
       response.locals.user = decodedToken.user
       response.locals.user = decodedToken.user
       response.locals.session = decodedToken.session
       response.locals.session = decodedToken.session
       response.locals.roles = decodedToken.roles
       response.locals.roles = decodedToken.roles
+      response.locals.isFreeUser =
+        decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser
     } catch (error) {
     } catch (error) {
       this.logger.error(
       this.logger.error(
         `Could not pass the request to websocket connection validation on underlying service: ${
         `Could not pass the request to websocket connection validation on underlying service: ${

+ 1 - 0
packages/api-gateway/src/Service/gRPC/GRPCSyncingServerServiceProxy.ts

@@ -31,6 +31,7 @@ export class GRPCSyncingServerServiceProxy {
         if (response.locals.session) {
         if (response.locals.session) {
           metadata.set('x-session-uuid', response.locals.session.uuid)
           metadata.set('x-session-uuid', response.locals.session.uuid)
         }
         }
+        metadata.set('x-is-free-user', response.locals.isFreeUser ? 'true' : 'false')
 
 
         this.syncingClient.syncItems(syncRequest, metadata, (error, syncResponse) => {
         this.syncingClient.syncItems(syncRequest, metadata, (error, syncResponse) => {
           if (error) {
           if (error) {

+ 4 - 0
packages/syncing-server/src/Bootstrap/Container.ts

@@ -458,6 +458,9 @@ export class ContainerConfigLoader {
     container
     container
       .bind(TYPES.Sync_REVISIONS_FREQUENCY)
       .bind(TYPES.Sync_REVISIONS_FREQUENCY)
       .toConstantValue(env.get('REVISIONS_FREQUENCY', true) ? +env.get('REVISIONS_FREQUENCY', true) : 300)
       .toConstantValue(env.get('REVISIONS_FREQUENCY', true) ? +env.get('REVISIONS_FREQUENCY', true) : 300)
+    container
+      .bind(TYPES.Sync_FREE_REVISIONS_FREQUENCY)
+      .toConstantValue(env.get('FREE_REVISIONS_FREQUENCY', true) ? +env.get('FREE_REVISIONS_FREQUENCY', true) : 86_400)
     container.bind(TYPES.Sync_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
     container.bind(TYPES.Sync_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
     container
     container
       .bind(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT)
       .bind(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT)
@@ -601,6 +604,7 @@ export class ContainerConfigLoader {
           container.get<TimerInterface>(TYPES.Sync_Timer),
           container.get<TimerInterface>(TYPES.Sync_Timer),
           container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
           container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
           container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
           container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
+          container.get<number>(TYPES.Sync_FREE_REVISIONS_FREQUENCY),
           container.get<number>(TYPES.Sync_REVISIONS_FREQUENCY),
           container.get<number>(TYPES.Sync_REVISIONS_FREQUENCY),
           container.get<DetermineSharedVaultOperationOnItem>(TYPES.Sync_DetermineSharedVaultOperationOnItem),
           container.get<DetermineSharedVaultOperationOnItem>(TYPES.Sync_DetermineSharedVaultOperationOnItem),
           container.get<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers),
           container.get<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers),

+ 1 - 0
packages/syncing-server/src/Bootstrap/Types.ts

@@ -34,6 +34,7 @@ const TYPES = {
   Sync_S3_BACKUP_BUCKET_NAME: Symbol.for('Sync_S3_BACKUP_BUCKET_NAME'),
   Sync_S3_BACKUP_BUCKET_NAME: Symbol.for('Sync_S3_BACKUP_BUCKET_NAME'),
   Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE: Symbol.for('Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE'),
   Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE: Symbol.for('Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE'),
   Sync_REVISIONS_FREQUENCY: Symbol.for('Sync_REVISIONS_FREQUENCY'),
   Sync_REVISIONS_FREQUENCY: Symbol.for('Sync_REVISIONS_FREQUENCY'),
+  Sync_FREE_REVISIONS_FREQUENCY: Symbol.for('Sync_FREE_REVISIONS_FREQUENCY'),
   Sync_VERSION: Symbol.for('Sync_VERSION'),
   Sync_VERSION: Symbol.for('Sync_VERSION'),
   Sync_CONTENT_SIZE_TRANSFER_LIMIT: Symbol.for('Sync_CONTENT_SIZE_TRANSFER_LIMIT'),
   Sync_CONTENT_SIZE_TRANSFER_LIMIT: Symbol.for('Sync_CONTENT_SIZE_TRANSFER_LIMIT'),
   Sync_MAX_ITEMS_LIMIT: Symbol.for('Sync_MAX_ITEMS_LIMIT'),
   Sync_MAX_ITEMS_LIMIT: Symbol.for('Sync_MAX_ITEMS_LIMIT'),

+ 13 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SaveItems/SaveItems.spec.ts

@@ -113,6 +113,7 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -137,6 +138,7 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -161,6 +163,7 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -182,6 +185,7 @@ describe('SaveItems', () => {
       readOnlyAccess: true,
       readOnlyAccess: true,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -204,6 +208,7 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -222,6 +227,7 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -240,10 +246,12 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
     expect(updateExistingItem.execute).toHaveBeenCalledWith({
     expect(updateExistingItem.execute).toHaveBeenCalledWith({
+      isFreeUser: false,
       itemHash: itemHash1,
       itemHash: itemHash1,
       existingItem: savedItem,
       existingItem: savedItem,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
@@ -284,10 +292,12 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
     expect(updateExistingItem.execute).toHaveBeenCalledWith({
     expect(updateExistingItem.execute).toHaveBeenCalledWith({
+      isFreeUser: false,
       itemHash: itemHash1,
       itemHash: itemHash1,
       existingItem: savedItem,
       existingItem: savedItem,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
@@ -310,6 +320,7 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -334,6 +345,7 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -379,6 +391,7 @@ describe('SaveItems', () => {
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: 'session-uuid',
       sessionUuid: 'session-uuid',
       snjsVersion: '2.200.0',
       snjsVersion: '2.200.0',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SaveItems/SaveItems.ts

@@ -84,6 +84,7 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
           itemHash,
           itemHash,
           sessionUuid: dto.sessionUuid,
           sessionUuid: dto.sessionUuid,
           performingUserUuid: dto.userUuid,
           performingUserUuid: dto.userUuid,
+          isFreeUser: dto.isFreeUser,
         })
         })
         if (udpatedItemOrError.isFailed()) {
         if (udpatedItemOrError.isFailed()) {
           this.logger.error(
           this.logger.error(

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SaveItems/SaveItemsDTO.ts

@@ -7,4 +7,5 @@ export interface SaveItemsDTO {
   readOnlyAccess: boolean
   readOnlyAccess: boolean
   sessionUuid: string | null
   sessionUuid: string | null
   snjsVersion: string
   snjsVersion: string
+  isFreeUser: boolean
 }
 }

+ 12 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SyncItems/SyncItems.spec.ts

@@ -156,6 +156,7 @@ describe('SyncItems', () => {
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       sessionUuid: null,
       sessionUuid: null,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
     })
     })
     expect(result.getValue()).toEqual({
     expect(result.getValue()).toEqual({
       conflicts: [],
       conflicts: [],
@@ -181,6 +182,7 @@ describe('SyncItems', () => {
       userUuid: '1-2-3',
       userUuid: '1-2-3',
       apiVersion: '20200115',
       apiVersion: '20200115',
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
       readOnlyAccess: false,
       readOnlyAccess: false,
       sessionUuid: null,
       sessionUuid: null,
     })
     })
@@ -205,6 +207,7 @@ describe('SyncItems', () => {
         apiVersion: ApiVersion.v20200115,
         apiVersion: ApiVersion.v20200115,
         sessionUuid: null,
         sessionUuid: null,
         snjsVersion: '1.2.3',
         snjsVersion: '1.2.3',
+        isFreeUser: false,
       })
       })
     } catch (error) {
     } catch (error) {
       caughtError = error
       caughtError = error
@@ -224,6 +227,7 @@ describe('SyncItems', () => {
       contentType: 'Note',
       contentType: 'Note',
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
     })
     })
     expect(result.getValue()).toEqual({
     expect(result.getValue()).toEqual({
       conflicts: [],
       conflicts: [],
@@ -249,6 +253,7 @@ describe('SyncItems', () => {
       contentType: 'Note',
       contentType: 'Note',
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
       sharedVaultUuids: ['00000000-0000-0000-0000-000000000000'],
       sharedVaultUuids: ['00000000-0000-0000-0000-000000000000'],
     })
     })
     expect(result.getValue()).toEqual({
     expect(result.getValue()).toEqual({
@@ -301,6 +306,7 @@ describe('SyncItems', () => {
       contentType: 'Note',
       contentType: 'Note',
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.getValue()).toEqual({
     expect(result.getValue()).toEqual({
@@ -340,6 +346,7 @@ describe('SyncItems', () => {
       contentType: 'Note',
       contentType: 'Note',
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -360,6 +367,7 @@ describe('SyncItems', () => {
       contentType: 'Note',
       contentType: 'Note',
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -380,6 +388,7 @@ describe('SyncItems', () => {
       contentType: 'Note',
       contentType: 'Note',
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -400,6 +409,7 @@ describe('SyncItems', () => {
       contentType: 'Note',
       contentType: 'Note',
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -420,6 +430,7 @@ describe('SyncItems', () => {
       contentType: 'Note',
       contentType: 'Note',
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -440,6 +451,7 @@ describe('SyncItems', () => {
       contentType: 'Note',
       contentType: 'Note',
       apiVersion: ApiVersion.v20200115,
       apiVersion: ApiVersion.v20200115,
       snjsVersion: '1.2.3',
       snjsVersion: '1.2.3',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SyncItems/SyncItems.ts

@@ -47,6 +47,7 @@ export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
         readOnlyAccess: dto.readOnlyAccess,
         readOnlyAccess: dto.readOnlyAccess,
         sessionUuid: dto.sessionUuid,
         sessionUuid: dto.sessionUuid,
         snjsVersion: dto.snjsVersion,
         snjsVersion: dto.snjsVersion,
+        isFreeUser: dto.isFreeUser,
       })
       })
       if (saveItemsResultOrError.isFailed()) {
       if (saveItemsResultOrError.isFailed()) {
         return Result.fail(saveItemsResultOrError.getError())
         return Result.fail(saveItemsResultOrError.getError())

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SyncItems/SyncItemsDTO.ts

@@ -13,4 +13,5 @@ export type SyncItemsDTO = {
   snjsVersion: string
   snjsVersion: string
   readOnlyAccess: boolean
   readOnlyAccess: boolean
   sessionUuid: string | null
   sessionUuid: string | null
+  isFreeUser: boolean
 }
 }

+ 26 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts

@@ -38,6 +38,7 @@ describe('UpdateExistingItem', () => {
       timer,
       timer,
       domainEventPublisher,
       domainEventPublisher,
       domainEventFactory,
       domainEventFactory,
+      86_400,
       5,
       5,
       determineSharedVaultOperationOnItem,
       determineSharedVaultOperationOnItem,
       addNotificationsForUsers,
       addNotificationsForUsers,
@@ -137,6 +138,7 @@ describe('UpdateExistingItem', () => {
       itemHash: itemHash1,
       itemHash: itemHash1,
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -151,6 +153,7 @@ describe('UpdateExistingItem', () => {
       itemHash: itemHash1,
       itemHash: itemHash1,
       sessionUuid: 'invalid-uuid',
       sessionUuid: 'invalid-uuid',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -167,6 +170,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -183,6 +187,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -206,6 +211,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -224,6 +230,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -241,6 +248,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -260,6 +268,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -280,6 +289,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -300,6 +310,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeFalsy()
     expect(result.isFailed()).toBeFalsy()
@@ -323,6 +334,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -347,6 +359,7 @@ describe('UpdateExistingItem', () => {
       }).getValue(),
       }).getValue(),
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: '00000000-0000-0000-0000-000000000000',
+      isFreeUser: false,
     })
     })
 
 
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
@@ -361,6 +374,7 @@ describe('UpdateExistingItem', () => {
       itemHash: itemHash1,
       itemHash: itemHash1,
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       sessionUuid: '00000000-0000-0000-0000-000000000000',
       performingUserUuid: 'invalid-uuid',
       performingUserUuid: 'invalid-uuid',
+      isFreeUser: false,
     })
     })
     expect(result.isFailed()).toBeTruthy()
     expect(result.isFailed()).toBeTruthy()
   })
   })
@@ -379,6 +393,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
       expect(result.isFailed()).toBeFalsy()
       expect(result.isFailed()).toBeFalsy()
       expect(item1.props.sharedVaultAssociation).not.toBeUndefined()
       expect(item1.props.sharedVaultAssociation).not.toBeUndefined()
@@ -405,6 +420,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
 
 
       expect(result.isFailed()).toBeFalsy()
       expect(result.isFailed()).toBeFalsy()
@@ -442,6 +458,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
 
 
       expect(result.isFailed()).toBeFalsy()
       expect(result.isFailed()).toBeFalsy()
@@ -470,6 +487,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
       expect(result.isFailed()).toBeTruthy()
       expect(result.isFailed()).toBeTruthy()
       mock.mockRestore()
       mock.mockRestore()
@@ -490,6 +508,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
       expect(result.isFailed()).toBeTruthy()
       expect(result.isFailed()).toBeTruthy()
     })
     })
@@ -521,6 +540,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
       expect(result.isFailed()).toBeTruthy()
       expect(result.isFailed()).toBeTruthy()
     })
     })
@@ -555,6 +575,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
       expect(result.isFailed()).toBeTruthy()
       expect(result.isFailed()).toBeTruthy()
 
 
@@ -576,6 +597,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
       expect(result.isFailed()).toBeTruthy()
       expect(result.isFailed()).toBeTruthy()
     })
     })
@@ -595,6 +617,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
       expect(result.isFailed()).toBeFalsy()
       expect(result.isFailed()).toBeFalsy()
       expect(item1.props.keySystemAssociation).not.toBeUndefined()
       expect(item1.props.keySystemAssociation).not.toBeUndefined()
@@ -616,6 +639,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
 
 
       expect(result.isFailed()).toBeFalsy()
       expect(result.isFailed()).toBeFalsy()
@@ -636,6 +660,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
       expect(result.isFailed()).toBeTruthy()
       expect(result.isFailed()).toBeTruthy()
     })
     })
@@ -658,6 +683,7 @@ describe('UpdateExistingItem', () => {
         itemHash,
         itemHash,
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         sessionUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
         performingUserUuid: '00000000-0000-0000-0000-000000000000',
+        isFreeUser: false,
       })
       })
       expect(result.isFailed()).toBeTruthy()
       expect(result.isFailed()).toBeTruthy()
       mock.mockRestore()
       mock.mockRestore()

+ 6 - 2
packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts

@@ -31,7 +31,8 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
     private timer: TimerInterface,
     private timer: TimerInterface,
     private domainEventPublisher: DomainEventPublisherInterface,
     private domainEventPublisher: DomainEventPublisherInterface,
     private domainEventFactory: DomainEventFactoryInterface,
     private domainEventFactory: DomainEventFactoryInterface,
-    private revisionFrequency: number,
+    private freeRevisionFrequency: number,
+    private premiumRevisionFrequency: number,
     private determineSharedVaultOperationOnItem: DetermineSharedVaultOperationOnItem,
     private determineSharedVaultOperationOnItem: DetermineSharedVaultOperationOnItem,
     private addNotificationForUsers: AddNotificationsForUsers,
     private addNotificationForUsers: AddNotificationsForUsers,
     private removeNotificationsForUser: RemoveNotificationsForUser,
     private removeNotificationsForUser: RemoveNotificationsForUser,
@@ -169,7 +170,10 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
 
 
     await this.itemRepository.update(dto.existingItem)
     await this.itemRepository.update(dto.existingItem)
 
 
-    if (secondsFromLastUpdate >= this.revisionFrequency) {
+    /* istanbul ignore next */
+    const revisionsFrequency = dto.isFreeUser ? this.freeRevisionFrequency : this.premiumRevisionFrequency
+
+    if (secondsFromLastUpdate >= revisionsFrequency) {
       if (
       if (
         dto.existingItem.props.contentType.value !== null &&
         dto.existingItem.props.contentType.value !== null &&
         [ContentType.TYPES.Note, ContentType.TYPES.File].includes(dto.existingItem.props.contentType.value)
         [ContentType.TYPES.Note, ContentType.TYPES.File].includes(dto.existingItem.props.contentType.value)

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItemDTO.ts

@@ -6,4 +6,5 @@ export interface UpdateExistingItemDTO {
   itemHash: ItemHash
   itemHash: ItemHash
   sessionUuid: string | null
   sessionUuid: string | null
   performingUserUuid: string
   performingUserUuid: string
+  isFreeUser: boolean
 }
 }

+ 1 - 0
packages/syncing-server/src/Infra/InversifyExpressUtils/Base/BaseItemsController.ts

@@ -70,6 +70,7 @@ export class BaseItemsController extends BaseHttpController {
       readOnlyAccess: response.locals.readOnlyAccess,
       readOnlyAccess: response.locals.readOnlyAccess,
       sessionUuid: response.locals.session ? response.locals.session.uuid : null,
       sessionUuid: response.locals.session ? response.locals.session.uuid : null,
       sharedVaultUuids,
       sharedVaultUuids,
+      isFreeUser: response.locals.isFreeUser,
     })
     })
     if (syncResult.isFailed()) {
     if (syncResult.isFailed()) {
       return this.json({ error: { message: syncResult.getError() } }, HttpStatusCode.BadRequest)
       return this.json({ error: { message: syncResult.getError() } }, HttpStatusCode.BadRequest)

+ 3 - 0
packages/syncing-server/src/Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware.ts

@@ -3,6 +3,7 @@ import { BaseMiddleware } from 'inversify-express-utils'
 import { verify } from 'jsonwebtoken'
 import { verify } from 'jsonwebtoken'
 import { CrossServiceTokenData } from '@standardnotes/security'
 import { CrossServiceTokenData } from '@standardnotes/security'
 import * as winston from 'winston'
 import * as winston from 'winston'
+import { RoleName } from '@standardnotes/domain-core'
 
 
 export class InversifyExpressAuthMiddleware extends BaseMiddleware {
 export class InversifyExpressAuthMiddleware extends BaseMiddleware {
   constructor(
   constructor(
@@ -26,6 +27,8 @@ export class InversifyExpressAuthMiddleware extends BaseMiddleware {
 
 
       response.locals.user = decodedToken.user
       response.locals.user = decodedToken.user
       response.locals.roles = decodedToken.roles
       response.locals.roles = decodedToken.roles
+      response.locals.isFreeUser =
+        decodedToken.roles.length === 1 && decodedToken.roles[0].name === RoleName.NAMES.CoreUser
       response.locals.session = decodedToken.session
       response.locals.session = decodedToken.session
       response.locals.readOnlyAccess = decodedToken.session?.readonly_access ?? false
       response.locals.readOnlyAccess = decodedToken.session?.readonly_access ?? false
       response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context
       response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context

+ 1 - 0
packages/syncing-server/src/Infra/gRPC/SyncingServer.ts

@@ -88,6 +88,7 @@ export class SyncingServer implements ISyncingServer {
         readOnlyAccess: call.metadata.get('x-read-only-access').pop() === 'true',
         readOnlyAccess: call.metadata.get('x-read-only-access').pop() === 'true',
         sessionUuid: call.metadata.get('x-session-uuid').pop() as string,
         sessionUuid: call.metadata.get('x-session-uuid').pop() as string,
         sharedVaultUuids,
         sharedVaultUuids,
+        isFreeUser: call.metadata.get('x-is-free-user').pop() === 'true',
       })
       })
       if (syncResult.isFailed()) {
       if (syncResult.isFailed()) {
         const metadata = new grpc.Metadata()
         const metadata = new grpc.Metadata()