Explorar o código

fix: user notifications structure (#667)

Karol Sójko hai 1 ano
pai
achega
1bbb639c83
Modificáronse 17 ficheiros con 155 adicións e 52 borrados
  1. 38 0
      packages/domain-core/src/Domain/Notification/NotificationPayload.ts
  2. 9 0
      packages/domain-core/src/Domain/Notification/NotificationPayloadProps.ts
  3. 0 0
      packages/domain-core/src/Domain/Notification/NotificationType.spec.ts
  4. 5 5
      packages/domain-core/src/Domain/Notification/NotificationType.ts
  5. 0 0
      packages/domain-core/src/Domain/Notification/NotificationTypeProps.ts
  6. 5 0
      packages/domain-core/src/Domain/index.ts
  7. 8 3
      packages/syncing-server/src/Domain/Notifications/Notification.spec.ts
  8. 2 4
      packages/syncing-server/src/Domain/Notifications/NotificationProps.ts
  9. 17 1
      packages/syncing-server/src/Domain/SharedVault/SharedVault.spec.ts
  10. 10 1
      packages/syncing-server/src/Domain/SharedVault/SharedVault.ts
  11. 12 19
      packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationForUser/AddNotificationForUser.spec.ts
  12. 1 7
      packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationForUser/AddNotificationForUser.ts
  13. 3 1
      packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationForUser/AddNotificationForUserDTO.ts
  14. 16 1
      packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.spec.ts
  15. 12 5
      packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.ts
  16. 1 1
      packages/syncing-server/src/Mapping/Http/NotificationHttpMapper.ts
  17. 16 4
      packages/syncing-server/src/Mapping/Persistence/NotificationPersistenceMapper.ts

+ 38 - 0
packages/domain-core/src/Domain/Notification/NotificationPayload.ts

@@ -0,0 +1,38 @@
+import { ValueObject } from '../Core/ValueObject'
+import { Result } from '../Core/Result'
+
+import { NotificationPayloadProps } from './NotificationPayloadProps'
+import { NotificationType } from './NotificationType'
+
+export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
+  private constructor(props: NotificationPayloadProps) {
+    super(props)
+  }
+
+  override toString(): string {
+    return JSON.stringify(this.props)
+  }
+
+  static createFromString(jsonPayload: string): Result<NotificationPayload> {
+    try {
+      const props = JSON.parse(jsonPayload)
+
+      return NotificationPayload.create(props)
+    } catch (error) {
+      return Result.fail<NotificationPayload>((error as Error).message)
+    }
+  }
+
+  static create(props: NotificationPayloadProps): Result<NotificationPayload> {
+    if (
+      props.itemUuid === undefined &&
+      props.type.equals(NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue())
+    ) {
+      return Result.fail<NotificationPayload>(
+        `Item uuid is required for ${NotificationType.TYPES.SharedVaultItemRemoved} notification type`,
+      )
+    }
+
+    return Result.ok<NotificationPayload>(new NotificationPayload(props))
+  }
+}

+ 9 - 0
packages/domain-core/src/Domain/Notification/NotificationPayloadProps.ts

@@ -0,0 +1,9 @@
+import { Uuid } from '../Common/Uuid'
+import { NotificationType } from './NotificationType'
+
+export interface NotificationPayloadProps {
+  type: NotificationType
+  sharedVaultUuid: Uuid
+  version: string
+  itemUuid?: Uuid
+}

+ 0 - 0
packages/syncing-server/src/Domain/Notifications/NotificationType.spec.ts → packages/domain-core/src/Domain/Notification/NotificationType.spec.ts


+ 5 - 5
packages/syncing-server/src/Domain/Notifications/NotificationType.ts → packages/domain-core/src/Domain/Notification/NotificationType.ts

@@ -1,5 +1,5 @@
-import { ValueObject, Result } from '@standardnotes/domain-core'
-
+import { Result } from '../Core/Result'
+import { ValueObject } from '../Core/ValueObject'
 import { NotificationTypeProps } from './NotificationTypeProps'
 import { NotificationTypeProps } from './NotificationTypeProps'
 
 
 export class NotificationType extends ValueObject<NotificationTypeProps> {
 export class NotificationType extends ValueObject<NotificationTypeProps> {
@@ -17,9 +17,9 @@ export class NotificationType extends ValueObject<NotificationTypeProps> {
   }
   }
 
 
   static create(notificationType: string): Result<NotificationType> {
   static create(notificationType: string): Result<NotificationType> {
-    const isValidPermission = Object.values(this.TYPES).includes(notificationType)
-    if (!isValidPermission) {
-      return Result.fail<NotificationType>(`Invalid shared vault user permission ${notificationType}`)
+    const isValidType = Object.values(this.TYPES).includes(notificationType)
+    if (!isValidType) {
+      return Result.fail<NotificationType>(`Invalid notification type: ${notificationType}`)
     } else {
     } else {
       return Result.ok<NotificationType>(new NotificationType({ value: notificationType }))
       return Result.ok<NotificationType>(new NotificationType({ value: notificationType }))
     }
     }

+ 0 - 0
packages/syncing-server/src/Domain/Notifications/NotificationTypeProps.ts → packages/domain-core/src/Domain/Notification/NotificationTypeProps.ts


+ 5 - 0
packages/domain-core/src/Domain/index.ts

@@ -45,6 +45,11 @@ export * from './Env/AbstractEnv'
 
 
 export * from './Mapping/MapperInterface'
 export * from './Mapping/MapperInterface'
 
 
+export * from './Notification/NotificationPayload'
+export * from './Notification/NotificationPayloadProps'
+export * from './Notification/NotificationType'
+export * from './Notification/NotificationTypeProps'
+
 export * from './Service/ServiceConfiguration'
 export * from './Service/ServiceConfiguration'
 export * from './Service/ServiceContainer'
 export * from './Service/ServiceContainer'
 export * from './Service/ServiceContainerInterface'
 export * from './Service/ServiceContainerInterface'

+ 8 - 3
packages/syncing-server/src/Domain/Notifications/Notification.spec.ts

@@ -1,14 +1,19 @@
-import { Timestamps, Uuid } from '@standardnotes/domain-core'
+import { NotificationPayload, NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core'
 
 
 import { Notification } from './Notification'
 import { Notification } from './Notification'
-import { NotificationType } from './NotificationType'
 
 
 describe('Notification', () => {
 describe('Notification', () => {
   it('should create an entity', () => {
   it('should create an entity', () => {
+    const payload = NotificationPayload.create({
+      sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
+      type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
+      version: '1.0',
+    }).getValue()
+
     const entityOrError = Notification.create({
     const entityOrError = Notification.create({
       timestamps: Timestamps.create(123456789, 123456789).getValue(),
       timestamps: Timestamps.create(123456789, 123456789).getValue(),
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-      payload: 'payload',
+      payload,
       type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(),
       type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(),
     })
     })
 
 

+ 2 - 4
packages/syncing-server/src/Domain/Notifications/NotificationProps.ts

@@ -1,10 +1,8 @@
-import { Timestamps, Uuid } from '@standardnotes/domain-core'
-
-import { NotificationType } from './NotificationType'
+import { NotificationPayload, NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core'
 
 
 export interface NotificationProps {
 export interface NotificationProps {
   userUuid: Uuid
   userUuid: Uuid
   type: NotificationType
   type: NotificationType
-  payload: string
+  payload: NotificationPayload
   timestamps: Timestamps
   timestamps: Timestamps
 }
 }

+ 17 - 1
packages/syncing-server/src/Domain/SharedVault/SharedVault.spec.ts

@@ -1,4 +1,4 @@
-import { Timestamps, Uuid } from '@standardnotes/domain-core'
+import { Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
 
 
 import { SharedVault } from './SharedVault'
 import { SharedVault } from './SharedVault'
 
 
@@ -13,5 +13,21 @@ describe('SharedVault', () => {
 
 
     expect(entityOrError.isFailed()).toBeFalsy()
     expect(entityOrError.isFailed()).toBeFalsy()
     expect(entityOrError.getValue().id).not.toBeNull()
     expect(entityOrError.getValue().id).not.toBeNull()
+    expect(entityOrError.getValue().uuid.value).toEqual(entityOrError.getValue().id.toString())
+  })
+
+  it('should throw an error if id cannot be cast to uuid', () => {
+    const entityOrError = SharedVault.create(
+      {
+        fileUploadBytesLimit: 1_000_000,
+        fileUploadBytesUsed: 0,
+        timestamps: Timestamps.create(123456789, 123456789).getValue(),
+        userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      },
+      new UniqueEntityId(1),
+    )
+
+    expect(entityOrError.isFailed()).toBeFalsy()
+    expect(() => entityOrError.getValue().uuid).toThrow()
   })
   })
 })
 })

+ 10 - 1
packages/syncing-server/src/Domain/SharedVault/SharedVault.ts

@@ -1,4 +1,4 @@
-import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
+import { Entity, Result, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
 
 
 import { SharedVaultProps } from './SharedVaultProps'
 import { SharedVaultProps } from './SharedVaultProps'
 
 
@@ -7,6 +7,15 @@ export class SharedVault extends Entity<SharedVaultProps> {
     super(props, id)
     super(props, id)
   }
   }
 
 
+  get uuid(): Uuid {
+    const uuidOrError = Uuid.create(this._id.toString())
+    if (uuidOrError.isFailed()) {
+      throw new Error(uuidOrError.getError())
+    }
+
+    return uuidOrError.getValue()
+  }
+
   static create(props: SharedVaultProps, id?: UniqueEntityId): Result<SharedVault> {
   static create(props: SharedVaultProps, id?: UniqueEntityId): Result<SharedVault> {
     return Result.ok<SharedVault>(new SharedVault(props, id))
     return Result.ok<SharedVault>(new SharedVault(props, id))
   }
   }

+ 12 - 19
packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationForUser/AddNotificationForUser.spec.ts

@@ -1,14 +1,14 @@
 import { TimerInterface } from '@standardnotes/time'
 import { TimerInterface } from '@standardnotes/time'
-import { Result } from '@standardnotes/domain-core'
+import { NotificationPayload, NotificationType, Result, Uuid } from '@standardnotes/domain-core'
 
 
 import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
 import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
 import { Notification } from '../../../Notifications/Notification'
 import { Notification } from '../../../Notifications/Notification'
 import { AddNotificationForUser } from './AddNotificationForUser'
 import { AddNotificationForUser } from './AddNotificationForUser'
-import { NotificationType } from '../../../Notifications/NotificationType'
 
 
 describe('AddNotificationForUser', () => {
 describe('AddNotificationForUser', () => {
   let notificationRepository: NotificationRepositoryInterface
   let notificationRepository: NotificationRepositoryInterface
   let timer: TimerInterface
   let timer: TimerInterface
+  let payload: NotificationPayload
 
 
   const createUseCase = () => new AddNotificationForUser(notificationRepository, timer)
   const createUseCase = () => new AddNotificationForUser(notificationRepository, timer)
 
 
@@ -18,6 +18,12 @@ describe('AddNotificationForUser', () => {
 
 
     timer = {} as jest.Mocked<TimerInterface>
     timer = {} as jest.Mocked<TimerInterface>
     timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
     timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
+
+    payload = NotificationPayload.create({
+      sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
+      type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
+      version: '1.0',
+    }).getValue()
   })
   })
 
 
   it('should save notification', async () => {
   it('should save notification', async () => {
@@ -26,7 +32,7 @@ describe('AddNotificationForUser', () => {
     const result = await useCase.execute({
     const result = await useCase.execute({
       userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
       userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
       type: NotificationType.TYPES.RemovedFromSharedVault,
       type: NotificationType.TYPES.RemovedFromSharedVault,
-      payload: 'payload',
+      payload,
       version: '1.0',
       version: '1.0',
     })
     })
 
 
@@ -39,7 +45,7 @@ describe('AddNotificationForUser', () => {
     const result = await useCase.execute({
     const result = await useCase.execute({
       userUuid: 'invalid',
       userUuid: 'invalid',
       type: NotificationType.TYPES.RemovedFromSharedVault,
       type: NotificationType.TYPES.RemovedFromSharedVault,
-      payload: 'payload',
+      payload,
       version: '1.0',
       version: '1.0',
     })
     })
 
 
@@ -52,20 +58,7 @@ describe('AddNotificationForUser', () => {
     const result = await useCase.execute({
     const result = await useCase.execute({
       userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
       userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
       type: 'invalid',
       type: 'invalid',
-      payload: 'payload',
-      version: '1.0',
-    })
-
-    expect(result.isFailed()).toBeTruthy()
-  })
-
-  it('should return error if notification payload is invalid', async () => {
-    const useCase = createUseCase()
-
-    const result = await useCase.execute({
-      userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
-      type: NotificationType.TYPES.RemovedFromSharedVault,
-      payload: '',
+      payload,
       version: '1.0',
       version: '1.0',
     })
     })
 
 
@@ -83,7 +76,7 @@ describe('AddNotificationForUser', () => {
     const result = await useCase.execute({
     const result = await useCase.execute({
       userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
       userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
       type: NotificationType.TYPES.RemovedFromSharedVault,
       type: NotificationType.TYPES.RemovedFromSharedVault,
-      payload: 'payload',
+      payload,
       version: '1.0',
       version: '1.0',
     })
     })
 
 

+ 1 - 7
packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationForUser/AddNotificationForUser.ts

@@ -1,10 +1,9 @@
-import { Result, Timestamps, UseCaseInterface, Uuid, Validator } from '@standardnotes/domain-core'
+import { NotificationType, Result, Timestamps, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
 import { TimerInterface } from '@standardnotes/time'
 import { TimerInterface } from '@standardnotes/time'
 
 
 import { AddNotificationForUserDTO } from './AddNotificationForUserDTO'
 import { AddNotificationForUserDTO } from './AddNotificationForUserDTO'
 import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
 import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
 import { Notification } from '../../../Notifications/Notification'
 import { Notification } from '../../../Notifications/Notification'
-import { NotificationType } from '../../../Notifications/NotificationType'
 
 
 export class AddNotificationForUser implements UseCaseInterface<Notification> {
 export class AddNotificationForUser implements UseCaseInterface<Notification> {
   constructor(private notificationRepository: NotificationRepositoryInterface, private timer: TimerInterface) {}
   constructor(private notificationRepository: NotificationRepositoryInterface, private timer: TimerInterface) {}
@@ -22,11 +21,6 @@ export class AddNotificationForUser implements UseCaseInterface<Notification> {
     }
     }
     const type = typeOrError.getValue()
     const type = typeOrError.getValue()
 
 
-    const paylodNotEmptyValidationResult = Validator.isNotEmpty(dto.payload)
-    if (paylodNotEmptyValidationResult.isFailed()) {
-      return Result.fail(paylodNotEmptyValidationResult.getError())
-    }
-
     const notificationOrError = Notification.create({
     const notificationOrError = Notification.create({
       userUuid,
       userUuid,
       type,
       type,

+ 3 - 1
packages/syncing-server/src/Domain/UseCase/Messaging/AddNotificationForUser/AddNotificationForUserDTO.ts

@@ -1,6 +1,8 @@
+import { NotificationPayload } from '@standardnotes/domain-core'
+
 export interface AddNotificationForUserDTO {
 export interface AddNotificationForUserDTO {
   version: string
   version: string
   type: string
   type: string
   userUuid: string
   userUuid: string
-  payload: string
+  payload: NotificationPayload
 }
 }

+ 16 - 1
packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.spec.ts

@@ -1,4 +1,4 @@
-import { Uuid, Timestamps, Result } from '@standardnotes/domain-core'
+import { Uuid, Timestamps, Result, NotificationPayload } from '@standardnotes/domain-core'
 
 
 import { SharedVault } from '../../../SharedVault/SharedVault'
 import { SharedVault } from '../../../SharedVault/SharedVault'
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
@@ -173,4 +173,19 @@ describe('RemoveUserFromSharedVault', () => {
 
 
     expect(result.isFailed()).toBe(true)
     expect(result.isFailed()).toBe(true)
   })
   })
+
+  it('should return error if notification payload could not be created', async () => {
+    const mock = jest.spyOn(NotificationPayload, 'create')
+    mock.mockReturnValue(Result.fail('Oops'))
+
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      originatorUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      userUuid: '00000000-0000-0000-0000-000000000001',
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Oops')
+  })
 })
 })

+ 12 - 5
packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.ts

@@ -1,9 +1,8 @@
-import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+import { NotificationPayload, NotificationType, Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
 
 
 import { RemoveUserFromSharedVaultDTO } from './RemoveUserFromSharedVaultDTO'
 import { RemoveUserFromSharedVaultDTO } from './RemoveUserFromSharedVaultDTO'
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
 import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
 import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
-import { NotificationType } from '../../../Notifications/NotificationType'
 import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
 import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
 
 
 export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
 export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
@@ -57,12 +56,20 @@ export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
 
 
     await this.sharedVaultUsersRepository.remove(sharedVaultUser)
     await this.sharedVaultUsersRepository.remove(sharedVaultUser)
 
 
+    const notificationPayloadOrError = NotificationPayload.create({
+      sharedVaultUuid: sharedVault.uuid,
+      type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
+      version: '1.0',
+    })
+    if (notificationPayloadOrError.isFailed()) {
+      return Result.fail(notificationPayloadOrError.getError())
+    }
+    const notificationPayload = notificationPayloadOrError.getValue()
+
     const result = await this.addNotificationForUser.execute({
     const result = await this.addNotificationForUser.execute({
       userUuid: sharedVaultUser.props.userUuid.value,
       userUuid: sharedVaultUser.props.userUuid.value,
       type: NotificationType.TYPES.RemovedFromSharedVault,
       type: NotificationType.TYPES.RemovedFromSharedVault,
-      payload: JSON.stringify({
-        sharedVaultUuid: sharedVault.id.toString(),
-      }),
+      payload: notificationPayload,
       version: '1.0',
       version: '1.0',
     })
     })
     if (result.isFailed()) {
     if (result.isFailed()) {

+ 1 - 1
packages/syncing-server/src/Mapping/Http/NotificationHttpMapper.ts

@@ -13,7 +13,7 @@ export class NotificationHttpMapper implements MapperInterface<Notification, Not
       uuid: domain.id.toString(),
       uuid: domain.id.toString(),
       user_uuid: domain.props.userUuid.value,
       user_uuid: domain.props.userUuid.value,
       type: domain.props.type.value,
       type: domain.props.type.value,
-      payload: domain.props.payload,
+      payload: domain.props.payload.toString(),
       created_at_timestamp: domain.props.timestamps.createdAt,
       created_at_timestamp: domain.props.timestamps.createdAt,
       updated_at_timestamp: domain.props.timestamps.updatedAt,
       updated_at_timestamp: domain.props.timestamps.updatedAt,
     }
     }

+ 16 - 4
packages/syncing-server/src/Mapping/Persistence/NotificationPersistenceMapper.ts

@@ -1,9 +1,15 @@
-import { Timestamps, MapperInterface, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
+import {
+  Timestamps,
+  MapperInterface,
+  UniqueEntityId,
+  Uuid,
+  NotificationType,
+  NotificationPayload,
+} from '@standardnotes/domain-core'
 
 
 import { Notification } from '../../Domain/Notifications/Notification'
 import { Notification } from '../../Domain/Notifications/Notification'
 
 
 import { TypeORMNotification } from '../../Infra/TypeORM/TypeORMNotification'
 import { TypeORMNotification } from '../../Infra/TypeORM/TypeORMNotification'
-import { NotificationType } from '../../Domain/Notifications/NotificationType'
 
 
 export class NotificationPersistenceMapper implements MapperInterface<Notification, TypeORMNotification> {
 export class NotificationPersistenceMapper implements MapperInterface<Notification, TypeORMNotification> {
   toDomain(projection: TypeORMNotification): Notification {
   toDomain(projection: TypeORMNotification): Notification {
@@ -25,10 +31,16 @@ export class NotificationPersistenceMapper implements MapperInterface<Notificati
     }
     }
     const type = typeOrError.getValue()
     const type = typeOrError.getValue()
 
 
+    const payloadOrError = NotificationPayload.createFromString(projection.payload)
+    if (payloadOrError.isFailed()) {
+      throw new Error(`Failed to create notification from projection: ${payloadOrError.getError()}`)
+    }
+    const payload = payloadOrError.getValue()
+
     const notificationOrError = Notification.create(
     const notificationOrError = Notification.create(
       {
       {
         userUuid,
         userUuid,
-        payload: projection.payload,
+        payload,
         type,
         type,
         timestamps,
         timestamps,
       },
       },
@@ -47,7 +59,7 @@ export class NotificationPersistenceMapper implements MapperInterface<Notificati
 
 
     typeorm.uuid = domain.id.toString()
     typeorm.uuid = domain.id.toString()
     typeorm.userUuid = domain.props.userUuid.value
     typeorm.userUuid = domain.props.userUuid.value
-    typeorm.payload = domain.props.payload
+    typeorm.payload = domain.props.payload.toString()
     typeorm.type = domain.props.type.value
     typeorm.type = domain.props.type.value
     typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
     typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
     typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
     typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt