mirror of
https://github.com/standardnotes/server
synced 2026-01-31 02:01:12 -05:00
Compare commits
8 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
352e02028d | ||
|
|
1bbb639c83 | ||
|
|
c14265f103 | ||
|
|
c030a6b3d8 | ||
|
|
af997ea658 | ||
|
|
efa4d7fc60 | ||
|
|
f714aaa0e9 | ||
|
|
aee6e60583 |
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.25.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.1...@standardnotes/analytics@2.25.2) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.0...@standardnotes/analytics@2.25.1) (2023-07-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.25.1",
|
||||
"version": "2.25.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.67.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.67.0...@standardnotes/api-gateway@1.67.1) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
# [1.67.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.66.1...@standardnotes/api-gateway@1.67.0) (2023-07-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add shared vaults, invites, messages and notifications to sync response ([#665](https://github.com/standardnotes/api-gateway/issues/665)) ([efa4d7f](https://github.com/standardnotes/api-gateway/commit/efa4d7fc6007ef668e3de3b04853ac11b2d13c30))
|
||||
|
||||
## [1.66.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.66.0...@standardnotes/api-gateway@1.66.1) (2023-07-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing imports and exports for controllers ([#664](https://github.com/standardnotes/api-gateway/issues/664)) ([aee6e60](https://github.com/standardnotes/api-gateway/commit/aee6e6058359e2b5231cc13387656f837699300f))
|
||||
|
||||
# [1.66.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.7...@standardnotes/api-gateway@1.66.0) (2023-07-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.66.0",
|
||||
"version": "1.67.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -13,6 +13,8 @@ export * from './v1/OfflineController'
|
||||
export * from './v1/PaymentsController'
|
||||
export * from './v1/RevisionsController'
|
||||
export * from './v1/SessionsController'
|
||||
export * from './v1/SharedVaultInvitesController'
|
||||
export * from './v1/SharedVaultUsersController'
|
||||
export * from './v1/SharedVaultsController'
|
||||
export * from './v1/SubscriptionInvitesController'
|
||||
export * from './v1/TokensController'
|
||||
|
||||
@@ -21,7 +21,7 @@ export class SharedVaultUsersController extends BaseHttpController {
|
||||
response,
|
||||
this.endpointResolver.resolveEndpointOrMethodIdentifier(
|
||||
'GET',
|
||||
'/shared-vaults/:sharedVaultUuid/users',
|
||||
'shared-vaults/:sharedVaultUuid/users',
|
||||
request.params.sharedVaultUuid,
|
||||
),
|
||||
request.body,
|
||||
@@ -35,7 +35,7 @@ export class SharedVaultUsersController extends BaseHttpController {
|
||||
response,
|
||||
this.endpointResolver.resolveEndpointOrMethodIdentifier(
|
||||
'DELETE',
|
||||
'/shared-vaults/:sharedVaultUuid/users/:userUuid',
|
||||
'shared-vaults/:sharedVaultUuid/users/:userUuid',
|
||||
request.params.sharedVaultUuid,
|
||||
request.params.userUuid,
|
||||
),
|
||||
|
||||
@@ -100,7 +100,7 @@ export class EndpointResolver implements EndpointResolverInterface {
|
||||
const identifier = this.endpointToIdentifierMap.get(`[${method}]:${endpoint}`)
|
||||
|
||||
if (!identifier) {
|
||||
throw new Error(`Endpoint ${endpoint} not found`)
|
||||
throw new Error(`Endpoint [${method}]:${endpoint} not found`)
|
||||
}
|
||||
|
||||
return identifier
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.126.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.126.0...@standardnotes/auth-server@1.126.1) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.126.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.125.1...@standardnotes/auth-server@1.126.0) (2023-07-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add shared vaults, invites, messages and notifications to sync response ([#665](https://github.com/standardnotes/server/issues/665)) ([efa4d7f](https://github.com/standardnotes/server/commit/efa4d7fc6007ef668e3de3b04853ac11b2d13c30))
|
||||
|
||||
## [1.125.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.125.0...@standardnotes/auth-server@1.125.1) (2023-07-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddNotifications1688540448427 implements MigrationInterface {
|
||||
name = 'AddNotifications1688540448427'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
|
||||
await queryRunner.query('DROP TABLE `notifications`')
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class RemoveNotifications1688540448428 implements MigrationInterface {
|
||||
name = 'RemoveNotifications1688540448428'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
|
||||
await queryRunner.query('DROP TABLE `notifications`')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddNotifications1688540623272 implements MigrationInterface {
|
||||
name = 'AddNotifications1688540623272'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
|
||||
)
|
||||
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
|
||||
await queryRunner.query('DROP TABLE "notifications"')
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class RemoveNotifications1688540623273 implements MigrationInterface {
|
||||
name = 'RemoveNotifications1688540623273'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
|
||||
await queryRunner.query('DROP TABLE "notifications"')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
|
||||
)
|
||||
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.125.1",
|
||||
"version": "1.126.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.23.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.23.1...@standardnotes/domain-core@1.23.2) (2023-07-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* user notifications structure ([#667](https://github.com/standardnotes/server/issues/667)) ([1bbb639](https://github.com/standardnotes/server/commit/1bbb639c83922ec09e3778f85419d76669d36ae3))
|
||||
|
||||
## [1.23.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.23.0...@standardnotes/domain-core@1.23.1) (2023-07-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-core",
|
||||
"version": "1.23.1",
|
||||
"version": "1.23.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Uuid } from '../Common/Uuid'
|
||||
import { NotificationType } from './NotificationType'
|
||||
|
||||
export interface NotificationPayloadProps {
|
||||
type: NotificationType
|
||||
sharedVaultUuid: Uuid
|
||||
version: string
|
||||
itemUuid?: Uuid
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
export class NotificationType extends ValueObject<NotificationTypeProps> {
|
||||
@@ -17,9 +17,9 @@ export class NotificationType extends ValueObject<NotificationTypeProps> {
|
||||
}
|
||||
|
||||
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 {
|
||||
return Result.ok<NotificationType>(new NotificationType({ value: notificationType }))
|
||||
}
|
||||
@@ -45,6 +45,11 @@ export * from './Env/AbstractEnv'
|
||||
|
||||
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/ServiceContainer'
|
||||
export * from './Service/ServiceContainerInterface'
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.11.9](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.8...@standardnotes/event-store@1.11.9) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.8](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.7...@standardnotes/event-store@1.11.8) (2023-07-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.11.8",
|
||||
"version": "1.11.9",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.19.11](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.10...@standardnotes/files-server@1.19.11) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.19.10](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.9...@standardnotes/files-server@1.19.10) (2023-07-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.19.10",
|
||||
"version": "1.19.11",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.13.4](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.3...@standardnotes/home-server@1.13.4) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.3](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.2...@standardnotes/home-server@1.13.3) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.2](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.1...@standardnotes/home-server@1.13.2) (2023-07-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.1](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.0...@standardnotes/home-server@1.13.1) (2023-07-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.12.6...@standardnotes/home-server@1.13.0) (2023-07-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.13.0",
|
||||
"version": "1.13.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.25.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.25.1...@standardnotes/revisions-server@1.25.2) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.25.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.25.0...@standardnotes/revisions-server@1.25.1) (2023-07-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.25.1",
|
||||
"version": "1.25.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.11](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.10...@standardnotes/scheduler-server@1.20.11) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.10](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.9...@standardnotes/scheduler-server@1.20.10) (2023-07-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.20.10",
|
||||
"version": "1.20.11",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.16](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.15...@standardnotes/settings@1.21.16) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.15](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.14...@standardnotes/settings@1.21.15) (2023-07-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/settings",
|
||||
"version": "1.21.15",
|
||||
"version": "1.21.16",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.68.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.1...@standardnotes/syncing-server@1.68.2) (2023-07-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* user notifications structure ([#667](https://github.com/standardnotes/syncing-server-js/issues/667)) ([1bbb639](https://github.com/standardnotes/syncing-server-js/commit/1bbb639c83922ec09e3778f85419d76669d36ae3))
|
||||
|
||||
## [1.68.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.0...@standardnotes/syncing-server@1.68.1) (2023-07-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** fetching items associated with shared vaults ([#666](https://github.com/standardnotes/syncing-server-js/issues/666)) ([c030a6b](https://github.com/standardnotes/syncing-server-js/commit/c030a6b3d838b1f09593905d28ace65097a3a940))
|
||||
|
||||
# [1.68.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.67.1...@standardnotes/syncing-server@1.68.0) (2023-07-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add shared vaults, invites, messages and notifications to sync response ([#665](https://github.com/standardnotes/syncing-server-js/issues/665)) ([efa4d7f](https://github.com/standardnotes/syncing-server-js/commit/efa4d7fc6007ef668e3de3b04853ac11b2d13c30))
|
||||
|
||||
## [1.67.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.67.0...@standardnotes/syncing-server@1.67.1) (2023-07-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing imports and exports for controllers ([#664](https://github.com/standardnotes/syncing-server-js/issues/664)) ([aee6e60](https://github.com/standardnotes/syncing-server-js/commit/aee6e6058359e2b5231cc13387656f837699300f))
|
||||
|
||||
# [1.67.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.66.0...@standardnotes/syncing-server@1.67.0) (2023-07-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -2,6 +2,10 @@ import 'reflect-metadata'
|
||||
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressHealthCheckController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressItemsController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressMessagesController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressSharedVaultInvitesController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressSharedVaultUsersController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressSharedVaultsController'
|
||||
|
||||
import helmet from 'helmet'
|
||||
import * as cors from 'cors'
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddNotifications1688540448427 implements MigrationInterface {
|
||||
name = 'AddNotifications1688540448427'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
|
||||
await queryRunner.query('DROP TABLE `notifications`')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddNotifications1689671563304 implements MigrationInterface {
|
||||
name = 'AddNotifications1689671563304'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE IF NOT EXISTS `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
|
||||
await queryRunner.query('DROP TABLE `notifications`')
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddNotifications1688540623272 implements MigrationInterface {
|
||||
name = 'AddNotifications1688540623272'
|
||||
export class AddNotifications1689672099828 implements MigrationInterface {
|
||||
name = 'AddNotifications1689672099828'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
|
||||
'CREATE TABLE IF NOT EXISTS "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
|
||||
)
|
||||
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.67.0",
|
||||
"version": "1.68.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -147,6 +147,9 @@ import { DeleteAllMessagesSentToUser } from '../Domain/UseCase/Messaging/DeleteA
|
||||
import { DeleteMessage } from '../Domain/UseCase/Messaging/DeleteMessage/DeleteMessage'
|
||||
import { MessageHttpRepresentation } from '../Mapping/Http/MessageHttpRepresentation'
|
||||
import { MessageHttpMapper } from '../Mapping/Http/MessageHttpMapper'
|
||||
import { GetUserNotifications } from '../Domain/UseCase/Messaging/GetUserNotifications/GetUserNotifications'
|
||||
import { NotificationHttpMapper } from '../Mapping/Http/NotificationHttpMapper'
|
||||
import { NotificationHttpRepresentation } from '../Mapping/Http/NotificationHttpRepresentation'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
|
||||
@@ -340,6 +343,9 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<MapperInterface<Message, MessageHttpRepresentation>>(TYPES.Sync_MessageHttpMapper)
|
||||
.toConstantValue(new MessageHttpMapper())
|
||||
container
|
||||
.bind<MapperInterface<Notification, NotificationHttpRepresentation>>(TYPES.Sync_NotificationHttpMapper)
|
||||
.toConstantValue(new NotificationHttpMapper())
|
||||
|
||||
// ORM
|
||||
container
|
||||
@@ -511,6 +517,7 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(
|
||||
new GetItems(
|
||||
container.get(TYPES.Sync_ItemRepository),
|
||||
container.get(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT),
|
||||
container.get(TYPES.Sync_ItemTransferCalculator),
|
||||
container.get(TYPES.Sync_Timer),
|
||||
@@ -550,6 +557,24 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetUserNotifications>(TYPES.Sync_GetUserNotifications)
|
||||
.toConstantValue(new GetUserNotifications(container.get(TYPES.Sync_NotificationRepository)))
|
||||
container
|
||||
.bind<GetSharedVaults>(TYPES.Sync_GetSharedVaults)
|
||||
.toConstantValue(
|
||||
new GetSharedVaults(
|
||||
container.get(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get(TYPES.Sync_SharedVaultRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetSharedVaultInvitesSentToUser>(TYPES.Sync_GetSharedVaultInvitesSentToUser)
|
||||
.toConstantValue(new GetSharedVaultInvitesSentToUser(container.get(TYPES.Sync_SharedVaultInviteRepository)))
|
||||
container
|
||||
.bind<GetMessagesSentToUser>(TYPES.Sync_GetMessagesSentToUser)
|
||||
.toConstantValue(new GetMessagesSentToUser(container.get(TYPES.Sync_MessageRepository)))
|
||||
|
||||
container
|
||||
.bind<SyncItems>(TYPES.Sync_SyncItems)
|
||||
.toConstantValue(
|
||||
@@ -557,6 +582,10 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Sync_ItemRepository),
|
||||
container.get(TYPES.Sync_GetItems),
|
||||
container.get(TYPES.Sync_SaveItems),
|
||||
container.get(TYPES.Sync_GetSharedVaults),
|
||||
container.get(TYPES.Sync_GetSharedVaultInvitesSentToUser),
|
||||
container.get(TYPES.Sync_GetMessagesSentToUser),
|
||||
container.get(TYPES.Sync_GetUserNotifications),
|
||||
),
|
||||
)
|
||||
container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
|
||||
@@ -621,9 +650,6 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<GetSharedVaultInvitesSentByUser>(TYPES.Sync_GetSharedVaultInvitesSentByUser)
|
||||
.toConstantValue(new GetSharedVaultInvitesSentByUser(container.get(TYPES.Sync_SharedVaultInviteRepository)))
|
||||
container
|
||||
.bind<GetSharedVaultInvitesSentToUser>(TYPES.Sync_GetSharedVaultInvitesSentToUser)
|
||||
.toConstantValue(new GetSharedVaultInvitesSentToUser(container.get(TYPES.Sync_SharedVaultInviteRepository)))
|
||||
container
|
||||
.bind<GetSharedVaultUsers>(TYPES.Sync_GetSharedVaultUsers)
|
||||
.toConstantValue(
|
||||
@@ -646,14 +672,6 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Sync_AddNotificationForUser),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetSharedVaults>(TYPES.Sync_GetSharedVaults)
|
||||
.toConstantValue(
|
||||
new GetSharedVaults(
|
||||
container.get(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get(TYPES.Sync_SharedVaultRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<CreateSharedVault>(TYPES.Sync_CreateSharedVault)
|
||||
.toConstantValue(
|
||||
@@ -683,9 +701,6 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Sync_VALET_TOKEN_TTL),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetMessagesSentToUser>(TYPES.Sync_GetMessagesSentToUser)
|
||||
.toConstantValue(new GetMessagesSentToUser(container.get(TYPES.Sync_MessageRepository)))
|
||||
container
|
||||
.bind<GetMessagesSentByUser>(TYPES.Sync_GetMessagesSentByUser)
|
||||
.toConstantValue(new GetMessagesSentByUser(container.get(TYPES.Sync_MessageRepository)))
|
||||
@@ -717,6 +732,10 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Sync_ItemHttpMapper),
|
||||
container.get(TYPES.Sync_ItemConflictHttpMapper),
|
||||
container.get(TYPES.Sync_SavedItemHttpMapper),
|
||||
container.get(TYPES.Sync_SharedVaultHttpMapper),
|
||||
container.get(TYPES.Sync_SharedVaultInviteHttpMapper),
|
||||
container.get(TYPES.Sync_MessageHttpMapper),
|
||||
container.get(TYPES.Sync_NotificationHttpMapper),
|
||||
),
|
||||
)
|
||||
container
|
||||
|
||||
@@ -75,6 +75,7 @@ const TYPES = {
|
||||
Sync_UpdateExistingItem: Symbol.for('Sync_UpdateExistingItem'),
|
||||
Sync_GetItems: Symbol.for('Sync_GetItems'),
|
||||
Sync_SaveItems: Symbol.for('Sync_SaveItems'),
|
||||
Sync_GetUserNotifications: Symbol.for('Sync_GetUserNotifications'),
|
||||
// Handlers
|
||||
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
|
||||
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
|
||||
@@ -113,6 +114,7 @@ const TYPES = {
|
||||
Sync_SharedVaultInviteHttpMapper: Symbol.for('Sync_SharedVaultInviteHttpMapper'),
|
||||
Sync_MessagePersistenceMapper: Symbol.for('Sync_MessagePersistenceMapper'),
|
||||
Sync_MessageHttpMapper: Symbol.for('Sync_MessageHttpMapper'),
|
||||
Sync_NotificationHttpMapper: Symbol.for('Sync_NotificationHttpMapper'),
|
||||
Sync_ItemPersistenceMapper: Symbol.for('Sync_ItemPersistenceMapper'),
|
||||
Sync_ItemHttpMapper: Symbol.for('Sync_ItemHttpMapper'),
|
||||
Sync_ItemHashHttpMapper: Symbol.for('Sync_ItemHashHttpMapper'),
|
||||
|
||||
47
packages/syncing-server/src/Domain/Item/Item.spec.ts
Normal file
47
packages/syncing-server/src/Domain/Item/Item.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ContentType, Dates, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { Item } from './Item'
|
||||
|
||||
describe('Item', () => {
|
||||
it('should create an aggregate', () => {
|
||||
const entityOrError = Item.create({
|
||||
duplicateOf: null,
|
||||
itemsKeyId: 'items-key-id',
|
||||
content: 'content',
|
||||
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
|
||||
encItemKey: 'enc-item-key',
|
||||
authHash: 'auth-hash',
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
deleted: false,
|
||||
updatedWithSession: null,
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
})
|
||||
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
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 = Item.create(
|
||||
{
|
||||
duplicateOf: null,
|
||||
itemsKeyId: 'items-key-id',
|
||||
content: 'content',
|
||||
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
|
||||
encItemKey: 'enc-item-key',
|
||||
authHash: 'auth-hash',
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
deleted: false,
|
||||
updatedWithSession: null,
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId(1),
|
||||
)
|
||||
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
expect(() => entityOrError.getValue().uuid).toThrow()
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,17 @@
|
||||
import { Aggregate, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
import { Aggregate, Result, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { ItemProps } from './ItemProps'
|
||||
|
||||
export class Item extends Aggregate<ItemProps> {
|
||||
get uuid(): Uuid {
|
||||
const uuidOrError = Uuid.create(this._id.toString())
|
||||
if (uuidOrError.isFailed()) {
|
||||
throw new Error(uuidOrError.getError())
|
||||
}
|
||||
|
||||
return uuidOrError.getValue()
|
||||
}
|
||||
|
||||
private constructor(props: ItemProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
@@ -11,4 +11,6 @@ export type ItemQuery = {
|
||||
limit?: number
|
||||
createdBetween?: Date[]
|
||||
selectString?: string
|
||||
includeSharedVaultUuids?: string[]
|
||||
exclusiveSharedVaultUuids?: string[]
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { ItemConflictHttpRepresentation } from '../../../Mapping/Http/ItemConflictHttpRepresentation'
|
||||
import { ItemHttpRepresentation } from '../../../Mapping/Http/ItemHttpRepresentation'
|
||||
import { MessageHttpRepresentation } from '../../../Mapping/Http/MessageHttpRepresentation'
|
||||
import { NotificationHttpRepresentation } from '../../../Mapping/Http/NotificationHttpRepresentation'
|
||||
import { SavedItemHttpRepresentation } from '../../../Mapping/Http/SavedItemHttpRepresentation'
|
||||
import { SharedVaultHttpRepresentation } from '../../../Mapping/Http/SharedVaultHttpRepresentation'
|
||||
import { SharedVaultInviteHttpRepresentation } from '../../../Mapping/Http/SharedVaultInviteHttpRepresentation'
|
||||
|
||||
export type SyncResponse20200115 = {
|
||||
retrieved_items: Array<ItemHttpRepresentation>
|
||||
saved_items: Array<SavedItemHttpRepresentation>
|
||||
conflicts: Array<ItemConflictHttpRepresentation>
|
||||
retrieved_items: ItemHttpRepresentation[]
|
||||
saved_items: SavedItemHttpRepresentation[]
|
||||
conflicts: ItemConflictHttpRepresentation[]
|
||||
sync_token: string
|
||||
cursor_token?: string
|
||||
messages: MessageHttpRepresentation[]
|
||||
shared_vaults: SharedVaultHttpRepresentation[]
|
||||
shared_vault_invites: SharedVaultInviteHttpRepresentation[]
|
||||
notifications: NotificationHttpRepresentation[]
|
||||
}
|
||||
|
||||
@@ -88,6 +88,10 @@ describe('SyncResponseFactory20161215', () => {
|
||||
],
|
||||
syncToken: 'sync-test',
|
||||
cursorToken: 'cursor-test',
|
||||
sharedVaults: [],
|
||||
sharedVaultInvites: [],
|
||||
messages: [],
|
||||
notifications: [],
|
||||
}),
|
||||
).toEqual({
|
||||
retrieved_items: [item1Projection],
|
||||
@@ -133,6 +137,10 @@ describe('SyncResponseFactory20161215', () => {
|
||||
],
|
||||
syncToken: 'sync-test',
|
||||
cursorToken: 'cursor-test',
|
||||
sharedVaults: [],
|
||||
sharedVaultInvites: [],
|
||||
messages: [],
|
||||
notifications: [],
|
||||
}),
|
||||
).toEqual({
|
||||
retrieved_items: [],
|
||||
|
||||
@@ -8,6 +8,14 @@ import { SyncResponseFactory20200115 } from './SyncResponseFactory20200115'
|
||||
import { ItemHttpRepresentation } from '../../../Mapping/Http/ItemHttpRepresentation'
|
||||
import { SavedItemHttpRepresentation } from '../../../Mapping/Http/SavedItemHttpRepresentation'
|
||||
import { ItemConflictHttpRepresentation } from '../../../Mapping/Http/ItemConflictHttpRepresentation'
|
||||
import { MessageHttpRepresentation } from '../../../Mapping/Http/MessageHttpRepresentation'
|
||||
import { NotificationHttpRepresentation } from '../../../Mapping/Http/NotificationHttpRepresentation'
|
||||
import { Notification } from '../../Notifications/Notification'
|
||||
import { SharedVaultHttpRepresentation } from '../../../Mapping/Http/SharedVaultHttpRepresentation'
|
||||
import { SharedVaultInviteHttpRepresentation } from '../../../Mapping/Http/SharedVaultInviteHttpRepresentation'
|
||||
import { Message } from '../../Message/Message'
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
|
||||
describe('SyncResponseFactory20200115', () => {
|
||||
let itemMapper: MapperInterface<Item, ItemHttpRepresentation>
|
||||
@@ -19,8 +27,25 @@ describe('SyncResponseFactory20200115', () => {
|
||||
let item1: Item
|
||||
let item2: Item
|
||||
let itemConflict: ItemConflict
|
||||
let sharedVault: SharedVault
|
||||
let sharedVaultInvite: SharedVaultInvite
|
||||
let message: Message
|
||||
let notification: Notification
|
||||
let sharedVaultMapper: MapperInterface<SharedVault, SharedVaultHttpRepresentation>
|
||||
let sharedVaultInvitesMapper: MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>
|
||||
let messageMapper: MapperInterface<Message, MessageHttpRepresentation>
|
||||
let notificationMapper: MapperInterface<Notification, NotificationHttpRepresentation>
|
||||
|
||||
const createFactory = () => new SyncResponseFactory20200115(itemMapper, itemConflictMapper, savedItemMapper)
|
||||
const createFactory = () =>
|
||||
new SyncResponseFactory20200115(
|
||||
itemMapper,
|
||||
itemConflictMapper,
|
||||
savedItemMapper,
|
||||
sharedVaultMapper,
|
||||
sharedVaultInvitesMapper,
|
||||
messageMapper,
|
||||
notificationMapper,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
itemProjection = {
|
||||
@@ -45,6 +70,27 @@ describe('SyncResponseFactory20200115', () => {
|
||||
item2 = {} as jest.Mocked<Item>
|
||||
|
||||
itemConflict = {} as jest.Mocked<ItemConflict>
|
||||
|
||||
sharedVaultMapper = {} as jest.Mocked<MapperInterface<SharedVault, SharedVaultHttpRepresentation>>
|
||||
sharedVaultMapper.toProjection = jest.fn().mockReturnValue({} as jest.Mocked<SharedVaultHttpRepresentation>)
|
||||
|
||||
sharedVaultInvitesMapper = {} as jest.Mocked<
|
||||
MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>
|
||||
>
|
||||
sharedVaultInvitesMapper.toProjection = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<SharedVaultInviteHttpRepresentation>)
|
||||
|
||||
messageMapper = {} as jest.Mocked<MapperInterface<Message, MessageHttpRepresentation>>
|
||||
messageMapper.toProjection = jest.fn().mockReturnValue({} as jest.Mocked<MessageHttpRepresentation>)
|
||||
|
||||
notificationMapper = {} as jest.Mocked<MapperInterface<Notification, NotificationHttpRepresentation>>
|
||||
notificationMapper.toProjection = jest.fn().mockReturnValue({} as jest.Mocked<NotificationHttpRepresentation>)
|
||||
|
||||
sharedVault = {} as jest.Mocked<SharedVault>
|
||||
sharedVaultInvite = {} as jest.Mocked<SharedVaultInvite>
|
||||
message = {} as jest.Mocked<Message>
|
||||
notification = {} as jest.Mocked<Notification>
|
||||
})
|
||||
|
||||
it('should turn sync items response into a sync response for API Version 20200115', async () => {
|
||||
@@ -55,6 +101,10 @@ describe('SyncResponseFactory20200115', () => {
|
||||
conflicts: [itemConflict],
|
||||
syncToken: 'sync-test',
|
||||
cursorToken: 'cursor-test',
|
||||
sharedVaults: [sharedVault],
|
||||
sharedVaultInvites: [sharedVaultInvite],
|
||||
messages: [message],
|
||||
notifications: [notification],
|
||||
}),
|
||||
).toEqual({
|
||||
retrieved_items: [itemProjection],
|
||||
@@ -62,6 +112,10 @@ describe('SyncResponseFactory20200115', () => {
|
||||
conflicts: [itemConflictProjection],
|
||||
sync_token: 'sync-test',
|
||||
cursor_token: 'cursor-test',
|
||||
shared_vaults: [{} as jest.Mocked<SharedVaultHttpRepresentation>],
|
||||
shared_vault_invites: [{} as jest.Mocked<SharedVaultInviteHttpRepresentation>],
|
||||
messages: [{} as jest.Mocked<MessageHttpRepresentation>],
|
||||
notifications: [{} as jest.Mocked<NotificationHttpRepresentation>],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,12 +8,24 @@ import { SyncItemsResponse } from '../../UseCase/Syncing/SyncItems/SyncItemsResp
|
||||
import { ItemHttpRepresentation } from '../../../Mapping/Http/ItemHttpRepresentation'
|
||||
import { ItemConflictHttpRepresentation } from '../../../Mapping/Http/ItemConflictHttpRepresentation'
|
||||
import { SavedItemHttpRepresentation } from '../../../Mapping/Http/SavedItemHttpRepresentation'
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
import { SharedVaultHttpRepresentation } from '../../../Mapping/Http/SharedVaultHttpRepresentation'
|
||||
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { SharedVaultInviteHttpRepresentation } from '../../../Mapping/Http/SharedVaultInviteHttpRepresentation'
|
||||
import { Message } from '../../Message/Message'
|
||||
import { MessageHttpRepresentation } from '../../../Mapping/Http/MessageHttpRepresentation'
|
||||
import { Notification } from '../../Notifications/Notification'
|
||||
import { NotificationHttpRepresentation } from '../../../Mapping/Http/NotificationHttpRepresentation'
|
||||
|
||||
export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface {
|
||||
constructor(
|
||||
private httpMapper: MapperInterface<Item, ItemHttpRepresentation>,
|
||||
private itemConflictMapper: MapperInterface<ItemConflict, ItemConflictHttpRepresentation>,
|
||||
private savedItemMapper: MapperInterface<Item, SavedItemHttpRepresentation>,
|
||||
private sharedVaultMapper: MapperInterface<SharedVault, SharedVaultHttpRepresentation>,
|
||||
private sharedVaultInvitesMapper: MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>,
|
||||
private messageMapper: MapperInterface<Message, MessageHttpRepresentation>,
|
||||
private notificationMapper: MapperInterface<Notification, NotificationHttpRepresentation>,
|
||||
) {}
|
||||
|
||||
async createResponse(syncItemsResponse: SyncItemsResponse): Promise<SyncResponse20200115> {
|
||||
@@ -32,12 +44,36 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
|
||||
conflicts.push(this.itemConflictMapper.toProjection(itemConflict))
|
||||
}
|
||||
|
||||
const sharedVaults = []
|
||||
for (const sharedVault of syncItemsResponse.sharedVaults) {
|
||||
sharedVaults.push(this.sharedVaultMapper.toProjection(sharedVault))
|
||||
}
|
||||
|
||||
const sharedVaultInvites = []
|
||||
for (const sharedVaultInvite of syncItemsResponse.sharedVaultInvites) {
|
||||
sharedVaultInvites.push(this.sharedVaultInvitesMapper.toProjection(sharedVaultInvite))
|
||||
}
|
||||
|
||||
const messages = []
|
||||
for (const contact of syncItemsResponse.messages) {
|
||||
messages.push(this.messageMapper.toProjection(contact))
|
||||
}
|
||||
|
||||
const notifications = []
|
||||
for (const notification of syncItemsResponse.notifications) {
|
||||
notifications.push(this.notificationMapper.toProjection(notification))
|
||||
}
|
||||
|
||||
return {
|
||||
retrieved_items: retrievedItems,
|
||||
saved_items: savedItems,
|
||||
conflicts,
|
||||
sync_token: syncItemsResponse.syncToken,
|
||||
cursor_token: syncItemsResponse.cursorToken,
|
||||
messages,
|
||||
shared_vaults: sharedVaults,
|
||||
shared_vault_invites: sharedVaultInvites,
|
||||
notifications,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Message } from './Message'
|
||||
export interface MessageRepositoryInterface {
|
||||
findByUuid: (uuid: Uuid) => Promise<Message | null>
|
||||
findByRecipientUuid: (uuid: Uuid) => Promise<Message[]>
|
||||
findByRecipientUuidUpdatedAfter: (uuid: Uuid, updatedAtTimestamp: number) => Promise<Message[]>
|
||||
findBySenderUuid: (uuid: Uuid) => Promise<Message[]>
|
||||
findByRecipientUuidAndReplaceabilityIdentifier: (dto: {
|
||||
recipientUuid: Uuid
|
||||
|
||||
@@ -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 { NotificationType } from './NotificationType'
|
||||
|
||||
describe('Notification', () => {
|
||||
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({
|
||||
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
payload: 'payload',
|
||||
payload,
|
||||
type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(),
|
||||
})
|
||||
|
||||
|
||||
@@ -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 {
|
||||
userUuid: Uuid
|
||||
type: NotificationType
|
||||
payload: string
|
||||
payload: NotificationPayload
|
||||
timestamps: Timestamps
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { Notification } from './Notification'
|
||||
|
||||
export interface NotificationRepositoryInterface {
|
||||
save(notification: Notification): Promise<void>
|
||||
findByUserUuidUpdatedAfter(userUuid: Uuid, lastSyncTime: number): Promise<Notification[]>
|
||||
findByUserUuid(userUuid: Uuid): Promise<Notification[]>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVault } from './SharedVault'
|
||||
|
||||
@@ -13,5 +13,21 @@ describe('SharedVault', () => {
|
||||
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -7,6 +7,15 @@ export class SharedVault extends Entity<SharedVaultProps> {
|
||||
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> {
|
||||
return Result.ok<SharedVault>(new SharedVault(props, id))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface SharedVaultInviteRepositoryInterface {
|
||||
remove(sharedVaultInvite: SharedVaultInvite): Promise<void>
|
||||
removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void>
|
||||
findByUserUuid(userUuid: Uuid): Promise<SharedVaultInvite[]>
|
||||
findByUserUuidUpdatedAfter(userUuid: Uuid, updatedAtTimestamp: number): Promise<SharedVaultInvite[]>
|
||||
findBySenderUuid(senderUuid: Uuid): Promise<SharedVaultInvite[]>
|
||||
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultInvite | null>
|
||||
findBySenderUuidAndSharedVaultUuid(dto: { senderUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultInvite[]>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
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 { Notification } from '../../../Notifications/Notification'
|
||||
import { AddNotificationForUser } from './AddNotificationForUser'
|
||||
import { NotificationType } from '../../../Notifications/NotificationType'
|
||||
|
||||
describe('AddNotificationForUser', () => {
|
||||
let notificationRepository: NotificationRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let payload: NotificationPayload
|
||||
|
||||
const createUseCase = () => new AddNotificationForUser(notificationRepository, timer)
|
||||
|
||||
@@ -18,6 +18,12 @@ describe('AddNotificationForUser', () => {
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
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 () => {
|
||||
@@ -26,7 +32,7 @@ describe('AddNotificationForUser', () => {
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload: 'payload',
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
@@ -39,7 +45,7 @@ describe('AddNotificationForUser', () => {
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload: 'payload',
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
@@ -52,20 +58,7 @@ describe('AddNotificationForUser', () => {
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
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',
|
||||
})
|
||||
|
||||
@@ -83,7 +76,7 @@ describe('AddNotificationForUser', () => {
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload: 'payload',
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
|
||||
@@ -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 { AddNotificationForUserDTO } from './AddNotificationForUserDTO'
|
||||
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
|
||||
import { Notification } from '../../../Notifications/Notification'
|
||||
import { NotificationType } from '../../../Notifications/NotificationType'
|
||||
|
||||
export class AddNotificationForUser implements UseCaseInterface<Notification> {
|
||||
constructor(private notificationRepository: NotificationRepositoryInterface, private timer: TimerInterface) {}
|
||||
@@ -22,11 +21,6 @@ export class AddNotificationForUser implements UseCaseInterface<Notification> {
|
||||
}
|
||||
const type = typeOrError.getValue()
|
||||
|
||||
const paylodNotEmptyValidationResult = Validator.isNotEmpty(dto.payload)
|
||||
if (paylodNotEmptyValidationResult.isFailed()) {
|
||||
return Result.fail(paylodNotEmptyValidationResult.getError())
|
||||
}
|
||||
|
||||
const notificationOrError = Notification.create({
|
||||
userUuid,
|
||||
type,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { NotificationPayload } from '@standardnotes/domain-core'
|
||||
|
||||
export interface AddNotificationForUserDTO {
|
||||
version: string
|
||||
type: string
|
||||
userUuid: string
|
||||
payload: string
|
||||
payload: NotificationPayload
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ describe('GetMessagesSentToUser', () => {
|
||||
beforeEach(() => {
|
||||
messageRepository = {} as jest.Mocked<MessageRepositoryInterface>
|
||||
messageRepository.findByRecipientUuid = jest.fn().mockReturnValue([])
|
||||
messageRepository.findByRecipientUuidUpdatedAfter = jest.fn().mockReturnValue([])
|
||||
})
|
||||
|
||||
it('should return messages sent to user', async () => {
|
||||
@@ -20,6 +21,16 @@ describe('GetMessagesSentToUser', () => {
|
||||
expect(result.getValue()).toEqual([])
|
||||
})
|
||||
|
||||
it('should return messages sent to user updated after given time', async () => {
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
lastSyncTime: 123,
|
||||
})
|
||||
|
||||
expect(result.getValue()).toEqual([])
|
||||
})
|
||||
|
||||
it('should return error when recipient uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
|
||||
@@ -14,8 +14,10 @@ export class GetMessagesSentToUser implements UseCaseInterface<Message[]> {
|
||||
}
|
||||
const recipientUuid = recipientUuidOrError.getValue()
|
||||
|
||||
const messages = await this.messageRepository.findByRecipientUuid(recipientUuid)
|
||||
if (dto.lastSyncTime) {
|
||||
return Result.ok(await this.messageRepository.findByRecipientUuidUpdatedAfter(recipientUuid, dto.lastSyncTime))
|
||||
}
|
||||
|
||||
return Result.ok(messages)
|
||||
return Result.ok(await this.messageRepository.findByRecipientUuid(recipientUuid))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface GetMessagesSentToUserDTO {
|
||||
recipientUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
|
||||
import { GetUserNotifications } from './GetUserNotifications'
|
||||
|
||||
describe('GetUserNotifications', () => {
|
||||
let notificationRepository: NotificationRepositoryInterface
|
||||
|
||||
const createUseCase = () => new GetUserNotifications(notificationRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
notificationRepository = {} as jest.Mocked<NotificationRepositoryInterface>
|
||||
notificationRepository.findByUserUuid = jest.fn().mockReturnValue([])
|
||||
notificationRepository.findByUserUuidUpdatedAfter = jest.fn().mockReturnValue([])
|
||||
})
|
||||
|
||||
it('should return notification for user', async () => {
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.getValue()).toEqual([])
|
||||
})
|
||||
|
||||
it('should return notifications for user updated after given time', async () => {
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
lastSyncTime: 123,
|
||||
})
|
||||
|
||||
expect(result.getValue()).toEqual([])
|
||||
})
|
||||
|
||||
it('should return error when user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { Notification } from '../../../Notifications/Notification'
|
||||
import { GetUserNotificationsDTO } from './GetUserNotificationsDTO'
|
||||
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
|
||||
|
||||
export class GetUserNotifications implements UseCaseInterface<Notification[]> {
|
||||
constructor(private notificationRepository: NotificationRepositoryInterface) {}
|
||||
|
||||
async execute(dto: GetUserNotificationsDTO): Promise<Result<Notification[]>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
if (dto.lastSyncTime) {
|
||||
return Result.ok(await this.notificationRepository.findByUserUuidUpdatedAfter(userUuid, dto.lastSyncTime))
|
||||
}
|
||||
|
||||
return Result.ok(await this.notificationRepository.findByUserUuid(userUuid))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface GetUserNotificationsDTO {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
||||
@@ -23,6 +23,7 @@ describe('GetSharedVaultInvitesSentToUser', () => {
|
||||
|
||||
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
|
||||
sharedVaultInviteRepository.findByUserUuid = jest.fn().mockResolvedValue([invite])
|
||||
sharedVaultInviteRepository.findByUserUuidUpdatedAfter = jest.fn().mockResolvedValue([invite])
|
||||
})
|
||||
|
||||
it('should return invites sent to user', async () => {
|
||||
@@ -35,6 +36,17 @@ describe('GetSharedVaultInvitesSentToUser', () => {
|
||||
expect(result.getValue()).toEqual([invite])
|
||||
})
|
||||
|
||||
it('should return invites sent to user updated after given time', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
lastSyncTime: 123,
|
||||
})
|
||||
|
||||
expect(result.getValue()).toEqual([invite])
|
||||
})
|
||||
|
||||
it('should return empty array if no invites found', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ export class GetSharedVaultInvitesSentToUser implements UseCaseInterface<SharedV
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
if (dto.lastSyncTime) {
|
||||
return Result.ok(await this.sharedVaultInviteRepository.findByUserUuidUpdatedAfter(userUuid, dto.lastSyncTime))
|
||||
}
|
||||
|
||||
return Result.ok(await this.sharedVaultInviteRepository.findByUserUuid(userUuid))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface GetSharedVaultInvitesSentToUserDTO {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
||||
|
||||
@@ -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 { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||
@@ -173,4 +173,19 @@ describe('RemoveUserFromSharedVault', () => {
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { NotificationType } from '../../../Notifications/NotificationType'
|
||||
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
|
||||
|
||||
export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
|
||||
@@ -57,12 +56,20 @@ export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
|
||||
|
||||
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({
|
||||
userUuid: sharedVaultUser.props.userUuid.value,
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload: JSON.stringify({
|
||||
sharedVaultUuid: sharedVault.id.toString(),
|
||||
}),
|
||||
payload: notificationPayload,
|
||||
version: '1.0',
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ItemTransferCalculatorInterface } from '../../../Item/ItemTransferCalcu
|
||||
import { GetItems } from './GetItems'
|
||||
import { Item } from '../../../Item/Item'
|
||||
import { ContentType, Dates, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
|
||||
describe('GetItems', () => {
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
@@ -12,9 +13,17 @@ describe('GetItems', () => {
|
||||
let timer: TimerInterface
|
||||
const maxItemsSyncLimit = 100
|
||||
let item: Item
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new GetItems(itemRepository, contentSizeTransferLimit, itemTransferCalculator, timer, maxItemsSyncLimit)
|
||||
new GetItems(
|
||||
itemRepository,
|
||||
sharedVaultUserRepository,
|
||||
contentSizeTransferLimit,
|
||||
itemTransferCalculator,
|
||||
timer,
|
||||
maxItemsSyncLimit,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
item = Item.create({
|
||||
@@ -41,13 +50,16 @@ describe('GetItems', () => {
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(123)
|
||||
|
||||
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
|
||||
sharedVaultUserRepository.findByUserUuid = jest.fn().mockResolvedValue([])
|
||||
})
|
||||
|
||||
it('returns items', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'user-uuid',
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
cursorToken: undefined,
|
||||
contentType: undefined,
|
||||
limit: 10,
|
||||
@@ -57,6 +69,7 @@ describe('GetItems', () => {
|
||||
expect(result.getValue()).toEqual({
|
||||
items: [item],
|
||||
cursorToken: undefined,
|
||||
lastSyncTime: null,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -66,7 +79,7 @@ describe('GetItems', () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'user-uuid',
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
cursorToken: undefined,
|
||||
contentType: undefined,
|
||||
limit: undefined,
|
||||
@@ -76,6 +89,7 @@ describe('GetItems', () => {
|
||||
expect(result.getValue()).toEqual({
|
||||
items: [item],
|
||||
cursorToken: 'MjowLjAwMDEyMw==',
|
||||
lastSyncTime: null,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -83,7 +97,7 @@ describe('GetItems', () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'user-uuid',
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
cursorToken: 'MjowLjAwMDEyMw==',
|
||||
contentType: undefined,
|
||||
limit: undefined,
|
||||
@@ -93,6 +107,7 @@ describe('GetItems', () => {
|
||||
expect(result.getValue()).toEqual({
|
||||
items: [item],
|
||||
cursorToken: undefined,
|
||||
lastSyncTime: 123.00000000000001,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -103,7 +118,7 @@ describe('GetItems', () => {
|
||||
const syncToken = Buffer.from(syncTokenData, 'utf-8').toString('base64')
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'user-uuid',
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
syncToken,
|
||||
contentType: undefined,
|
||||
limit: undefined,
|
||||
@@ -113,6 +128,7 @@ describe('GetItems', () => {
|
||||
expect(result.getValue()).toEqual({
|
||||
items: [item],
|
||||
cursorToken: undefined,
|
||||
lastSyncTime: 123,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -123,7 +139,7 @@ describe('GetItems', () => {
|
||||
const syncToken = Buffer.from(syncTokenData, 'utf-8').toString('base64')
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'user-uuid',
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
syncToken,
|
||||
contentType: undefined,
|
||||
limit: undefined,
|
||||
@@ -137,7 +153,7 @@ describe('GetItems', () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'user-uuid',
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
cursorToken: undefined,
|
||||
contentType: undefined,
|
||||
limit: 200,
|
||||
@@ -147,6 +163,48 @@ describe('GetItems', () => {
|
||||
expect(result.getValue()).toEqual({
|
||||
items: [item],
|
||||
cursorToken: undefined,
|
||||
lastSyncTime: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('should return error for invalid user uuid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
cursorToken: undefined,
|
||||
contentType: undefined,
|
||||
limit: undefined,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('should filter shared vault uuids user wants to sync with the ones it has access to', async () => {
|
||||
sharedVaultUserRepository.findByUserUuid = jest.fn().mockResolvedValue([
|
||||
{
|
||||
props: {
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
cursorToken: undefined,
|
||||
contentType: undefined,
|
||||
limit: undefined,
|
||||
sharedVaultUuids: ['00000000-0000-0000-0000-000000000000', '11111111-1111-1111-1111-111111111111'],
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(result.getValue()).toEqual({
|
||||
items: [item],
|
||||
cursorToken: undefined,
|
||||
lastSyncTime: null,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { Time, TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { Item } from '../../../Item/Item'
|
||||
@@ -7,6 +7,7 @@ import { ItemQuery } from '../../../Item/ItemQuery'
|
||||
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
|
||||
import { ItemTransferCalculatorInterface } from '../../../Item/ItemTransferCalculatorInterface'
|
||||
import { GetItemsDTO } from './GetItemsDTO'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
|
||||
export class GetItems implements UseCaseInterface<GetItemsResult> {
|
||||
private readonly DEFAULT_ITEMS_LIMIT = 150
|
||||
@@ -14,6 +15,7 @@ export class GetItems implements UseCaseInterface<GetItemsResult> {
|
||||
|
||||
constructor(
|
||||
private itemRepository: ItemRepositoryInterface,
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private contentSizeTransferLimit: number,
|
||||
private itemTransferCalculator: ItemTransferCalculatorInterface,
|
||||
private timer: TimerInterface,
|
||||
@@ -27,12 +29,25 @@ export class GetItems implements UseCaseInterface<GetItemsResult> {
|
||||
}
|
||||
const lastSyncTime = lastSyncTimeOrError.getValue()
|
||||
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const syncTimeComparison = dto.cursorToken ? '>=' : '>'
|
||||
const limit = dto.limit === undefined || dto.limit < 1 ? this.DEFAULT_ITEMS_LIMIT : dto.limit
|
||||
const upperBoundLimit = limit < this.maxItemsSyncLimit ? limit : this.maxItemsSyncLimit
|
||||
|
||||
const sharedVaultUsers = await this.sharedVaultUserRepository.findByUserUuid(userUuid)
|
||||
const userSharedVaultUuids = sharedVaultUsers.map((sharedVaultUser) => sharedVaultUser.props.sharedVaultUuid.value)
|
||||
|
||||
const exclusiveSharedVaultUuids = dto.sharedVaultUuids
|
||||
? dto.sharedVaultUuids.filter((sharedVaultUuid) => userSharedVaultUuids.includes(sharedVaultUuid))
|
||||
: undefined
|
||||
|
||||
const itemQuery: ItemQuery = {
|
||||
userUuid: dto.userUuid,
|
||||
userUuid: userUuid.value,
|
||||
lastSyncTime: lastSyncTime ?? undefined,
|
||||
syncTimeComparison,
|
||||
contentType: dto.contentType,
|
||||
@@ -40,6 +55,8 @@ export class GetItems implements UseCaseInterface<GetItemsResult> {
|
||||
sortBy: 'updated_at_timestamp',
|
||||
sortOrder: 'ASC',
|
||||
limit: upperBoundLimit,
|
||||
includeSharedVaultUuids: !dto.sharedVaultUuids ? userSharedVaultUuids : undefined,
|
||||
exclusiveSharedVaultUuids,
|
||||
}
|
||||
|
||||
const itemUuidsToFetch = await this.itemTransferCalculator.computeItemUuidsToFetch(
|
||||
@@ -65,6 +82,7 @@ export class GetItems implements UseCaseInterface<GetItemsResult> {
|
||||
return Result.ok({
|
||||
items,
|
||||
cursorToken,
|
||||
lastSyncTime,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@ export interface GetItemsDTO {
|
||||
cursorToken?: string | null
|
||||
limit?: number
|
||||
contentType?: string
|
||||
sharedVaultUuids?: string[]
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@ import { Item } from '../../../Item/Item'
|
||||
export interface GetItemsResult {
|
||||
items: Item[]
|
||||
cursorToken?: string
|
||||
lastSyncTime: number | null
|
||||
}
|
||||
|
||||
@@ -31,15 +31,6 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
||||
const lastUpdatedTimestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
for (const itemHash of dto.itemHashes) {
|
||||
if (dto.readOnlyAccess) {
|
||||
conflicts.push({
|
||||
unsavedItem: itemHash,
|
||||
type: ConflictType.ReadOnlyError,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
const itemUuidOrError = Uuid.create(itemHash.props.uuid)
|
||||
if (itemUuidOrError.isFailed()) {
|
||||
conflicts.push({
|
||||
@@ -52,6 +43,17 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
||||
const itemUuid = itemUuidOrError.getValue()
|
||||
|
||||
const existingItem = await this.itemRepository.findByUuid(itemUuid)
|
||||
|
||||
if (dto.readOnlyAccess) {
|
||||
conflicts.push({
|
||||
unsavedItem: itemHash,
|
||||
serverItem: existingItem ?? undefined,
|
||||
type: ConflictType.ReadOnlyError,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
const processingResult = await this.itemSaveValidator.validate({
|
||||
userUuid: dto.userUuid,
|
||||
apiVersion: dto.apiVersion,
|
||||
|
||||
@@ -9,6 +9,10 @@ import { ContentType, Dates, Result, Timestamps, UniqueEntityId, Uuid } from '@s
|
||||
import { GetItems } from '../GetItems/GetItems'
|
||||
import { SaveItems } from '../SaveItems/SaveItems'
|
||||
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
|
||||
import { GetSharedVaults } from '../../SharedVaults/GetSharedVaults/GetSharedVaults'
|
||||
import { GetMessagesSentToUser } from '../../Messaging/GetMessagesSentToUser/GetMessagesSentToUser'
|
||||
import { GetUserNotifications } from '../../Messaging/GetUserNotifications/GetUserNotifications'
|
||||
import { GetSharedVaultInvitesSentToUser } from '../../SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser'
|
||||
|
||||
describe('SyncItems', () => {
|
||||
let getItemsUseCase: GetItems
|
||||
@@ -18,8 +22,21 @@ describe('SyncItems', () => {
|
||||
let item2: Item
|
||||
let item3: Item
|
||||
let itemHash: ItemHash
|
||||
let getSharedVaultsUseCase: GetSharedVaults
|
||||
let getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser
|
||||
let getMessagesSentToUser: GetMessagesSentToUser
|
||||
let getUserNotifications: GetUserNotifications
|
||||
|
||||
const createUseCase = () => new SyncItems(itemRepository, getItemsUseCase, saveItemsUseCase)
|
||||
const createUseCase = () =>
|
||||
new SyncItems(
|
||||
itemRepository,
|
||||
getItemsUseCase,
|
||||
saveItemsUseCase,
|
||||
getSharedVaultsUseCase,
|
||||
getSharedVaultInvitesSentToUserUseCase,
|
||||
getMessagesSentToUser,
|
||||
getUserNotifications,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
item1 = Item.create(
|
||||
@@ -104,6 +121,18 @@ describe('SyncItems', () => {
|
||||
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.findAll = jest.fn().mockReturnValue([item3, item1])
|
||||
|
||||
getSharedVaultsUseCase = {} as jest.Mocked<GetSharedVaults>
|
||||
getSharedVaultsUseCase.execute = jest.fn().mockReturnValue(Result.ok([]))
|
||||
|
||||
getSharedVaultInvitesSentToUserUseCase = {} as jest.Mocked<GetSharedVaultInvitesSentToUser>
|
||||
getSharedVaultInvitesSentToUserUseCase.execute = jest.fn().mockReturnValue(Result.ok([]))
|
||||
|
||||
getMessagesSentToUser = {} as jest.Mocked<GetMessagesSentToUser>
|
||||
getMessagesSentToUser.execute = jest.fn().mockReturnValue(Result.ok([]))
|
||||
|
||||
getUserNotifications = {} as jest.Mocked<GetUserNotifications>
|
||||
getUserNotifications.execute = jest.fn().mockReturnValue(Result.ok([]))
|
||||
})
|
||||
|
||||
it('should sync items', async () => {
|
||||
@@ -126,6 +155,10 @@ describe('SyncItems', () => {
|
||||
retrievedItems: [item1],
|
||||
savedItems: [item2],
|
||||
syncToken: 'qwerty',
|
||||
sharedVaults: [],
|
||||
sharedVaultInvites: [],
|
||||
notifications: [],
|
||||
messages: [],
|
||||
})
|
||||
|
||||
expect(getItemsUseCase.execute).toHaveBeenCalledWith({
|
||||
@@ -162,6 +195,10 @@ describe('SyncItems', () => {
|
||||
retrievedItems: [item3, item1],
|
||||
savedItems: [item2],
|
||||
syncToken: 'qwerty',
|
||||
sharedVaults: [],
|
||||
sharedVaultInvites: [],
|
||||
notifications: [],
|
||||
messages: [],
|
||||
})
|
||||
})
|
||||
|
||||
@@ -219,6 +256,10 @@ describe('SyncItems', () => {
|
||||
retrievedItems: [item1],
|
||||
savedItems: [],
|
||||
syncToken: 'qwerty',
|
||||
sharedVaults: [],
|
||||
sharedVaultInvites: [],
|
||||
notifications: [],
|
||||
messages: [],
|
||||
})
|
||||
})
|
||||
|
||||
@@ -261,4 +302,84 @@ describe('SyncItems', () => {
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error if get shared vaults fails', async () => {
|
||||
getSharedVaultsUseCase.execute = jest.fn().mockReturnValue(Result.fail('error'))
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: '2-3-4',
|
||||
cursorToken: 'bar',
|
||||
limit: 10,
|
||||
contentType: 'Note',
|
||||
apiVersion: ApiVersion.v20200115,
|
||||
snjsVersion: '1.2.3',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error if get shared vault invites fails', async () => {
|
||||
getSharedVaultInvitesSentToUserUseCase.execute = jest.fn().mockReturnValue(Result.fail('error'))
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: '2-3-4',
|
||||
cursorToken: 'bar',
|
||||
limit: 10,
|
||||
contentType: 'Note',
|
||||
apiVersion: ApiVersion.v20200115,
|
||||
snjsVersion: '1.2.3',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error if get messages fails', async () => {
|
||||
getMessagesSentToUser.execute = jest.fn().mockReturnValue(Result.fail('error'))
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: '2-3-4',
|
||||
cursorToken: 'bar',
|
||||
limit: 10,
|
||||
contentType: 'Note',
|
||||
apiVersion: ApiVersion.v20200115,
|
||||
snjsVersion: '1.2.3',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error if get user notifications fails', async () => {
|
||||
getUserNotifications.execute = jest.fn().mockReturnValue(Result.fail('error'))
|
||||
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: '2-3-4',
|
||||
cursorToken: 'bar',
|
||||
limit: 10,
|
||||
contentType: 'Note',
|
||||
apiVersion: ApiVersion.v20200115,
|
||||
snjsVersion: '1.2.3',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,12 +7,20 @@ import { SyncItemsResponse } from './SyncItemsResponse'
|
||||
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
|
||||
import { GetItems } from '../GetItems/GetItems'
|
||||
import { SaveItems } from '../SaveItems/SaveItems'
|
||||
import { GetSharedVaults } from '../../SharedVaults/GetSharedVaults/GetSharedVaults'
|
||||
import { GetSharedVaultInvitesSentToUser } from '../../SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser'
|
||||
import { GetMessagesSentToUser } from '../../Messaging/GetMessagesSentToUser/GetMessagesSentToUser'
|
||||
import { GetUserNotifications } from '../../Messaging/GetUserNotifications/GetUserNotifications'
|
||||
|
||||
export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
|
||||
constructor(
|
||||
private itemRepository: ItemRepositoryInterface,
|
||||
private getItemsUseCase: GetItems,
|
||||
private saveItemsUseCase: SaveItems,
|
||||
private getSharedVaultsUseCase: GetSharedVaults,
|
||||
private getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser,
|
||||
private getMessagesSentToUser: GetMessagesSentToUser,
|
||||
private getUserNotifications: GetUserNotifications,
|
||||
) {}
|
||||
|
||||
async execute(dto: SyncItemsDTO): Promise<Result<SyncItemsResponse>> {
|
||||
@@ -22,6 +30,7 @@ export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
|
||||
cursorToken: dto.cursorToken,
|
||||
limit: dto.limit,
|
||||
contentType: dto.contentType,
|
||||
sharedVaultUuids: dto.sharedVaultUuids,
|
||||
})
|
||||
if (getItemsResultOrError.isFailed()) {
|
||||
return Result.fail(getItemsResultOrError.getError())
|
||||
@@ -45,12 +54,52 @@ export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
|
||||
retrievedItems = await this.frontLoadKeysItemsToTop(dto.userUuid, retrievedItems)
|
||||
}
|
||||
|
||||
const sharedVaultsOrError = await this.getSharedVaultsUseCase.execute({
|
||||
userUuid: dto.userUuid,
|
||||
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
||||
})
|
||||
if (sharedVaultsOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultsOrError.getError())
|
||||
}
|
||||
const sharedVaults = sharedVaultsOrError.getValue()
|
||||
|
||||
const sharedVaultInvitesOrError = await this.getSharedVaultInvitesSentToUserUseCase.execute({
|
||||
userUuid: dto.userUuid,
|
||||
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
||||
})
|
||||
if (sharedVaultInvitesOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultInvitesOrError.getError())
|
||||
}
|
||||
const sharedVaultInvites = sharedVaultInvitesOrError.getValue()
|
||||
|
||||
const messagesOrError = await this.getMessagesSentToUser.execute({
|
||||
recipientUuid: dto.userUuid,
|
||||
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
||||
})
|
||||
if (messagesOrError.isFailed()) {
|
||||
return Result.fail(messagesOrError.getError())
|
||||
}
|
||||
const messages = messagesOrError.getValue()
|
||||
|
||||
const notificationsOrError = await this.getUserNotifications.execute({
|
||||
userUuid: dto.userUuid,
|
||||
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
|
||||
})
|
||||
if (notificationsOrError.isFailed()) {
|
||||
return Result.fail(notificationsOrError.getError())
|
||||
}
|
||||
const notifications = notificationsOrError.getValue()
|
||||
|
||||
const syncResponse: SyncItemsResponse = {
|
||||
retrievedItems,
|
||||
syncToken: saveItemsResult.syncToken,
|
||||
savedItems: saveItemsResult.savedItems,
|
||||
conflicts: saveItemsResult.conflicts,
|
||||
cursorToken: getItemsResult.cursorToken,
|
||||
sharedVaultInvites,
|
||||
sharedVaults,
|
||||
messages,
|
||||
notifications,
|
||||
}
|
||||
|
||||
return Result.ok(syncResponse)
|
||||
|
||||
@@ -5,7 +5,7 @@ export type SyncItemsDTO = {
|
||||
itemHashes: Array<ItemHash>
|
||||
computeIntegrityHash: boolean
|
||||
limit: number
|
||||
sharedVaultUuids?: string[] | null
|
||||
sharedVaultUuids?: string[]
|
||||
syncToken?: string | null
|
||||
cursorToken?: string | null
|
||||
contentType?: string
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { Item } from '../../../Item/Item'
|
||||
import { ItemConflict } from '../../../Item/ItemConflict'
|
||||
import { Message } from '../../../Message/Message'
|
||||
import { Notification } from '../../../Notifications/Notification'
|
||||
import { SharedVault } from '../../../SharedVault/SharedVault'
|
||||
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
|
||||
export type SyncItemsResponse = {
|
||||
retrievedItems: Array<Item>
|
||||
savedItems: Array<Item>
|
||||
conflicts: Array<ItemConflict>
|
||||
syncToken: string
|
||||
sharedVaults: SharedVault[]
|
||||
sharedVaultInvites: SharedVaultInvite[]
|
||||
messages: Message[]
|
||||
notifications: Notification[]
|
||||
cursorToken?: string
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
|
||||
import { ControllerContainerInterface, MapperInterface, Validator } from '@standardnotes/domain-core'
|
||||
import { BaseHttpController, results } from 'inversify-express-utils'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
@@ -49,19 +49,27 @@ export class HomeServerItemsController extends BaseHttpController {
|
||||
}
|
||||
}
|
||||
|
||||
let sharedVaultUuids: string[] | undefined = undefined
|
||||
if ('shared_vault_uuids' in request.body) {
|
||||
const sharedVaultUuidsValidation = Validator.isNotEmpty(sharedVaultUuids)
|
||||
if (!sharedVaultUuidsValidation.isFailed()) {
|
||||
sharedVaultUuids = request.body.shared_vault_uuids
|
||||
}
|
||||
}
|
||||
|
||||
const syncResult = await this.syncItems.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
itemHashes,
|
||||
computeIntegrityHash: request.body.compute_integrity === true,
|
||||
syncToken: request.body.sync_token,
|
||||
cursorToken: request.body.cursor_token,
|
||||
sharedVaultUuids: request.body.shared_vault_uuids,
|
||||
limit: request.body.limit,
|
||||
contentType: request.body.content_type,
|
||||
apiVersion: request.body.api ?? ApiVersion.v20161215,
|
||||
snjsVersion: <string>request.headers['x-snjs-version'],
|
||||
readOnlyAccess: response.locals.readOnlyAccess,
|
||||
sessionUuid: response.locals.session ? response.locals.session.uuid : null,
|
||||
sharedVaultUuids,
|
||||
})
|
||||
if (syncResult.isFailed()) {
|
||||
return this.json({ error: { message: syncResult.getError() } }, HttpStatusCode.BadRequest)
|
||||
@@ -102,7 +110,12 @@ export class HomeServerItemsController extends BaseHttpController {
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return this.json({ error: { message: result.getError() } }, 404)
|
||||
return this.json(
|
||||
{
|
||||
error: { message: 'Item not found' },
|
||||
},
|
||||
404,
|
||||
)
|
||||
}
|
||||
|
||||
return this.json({ item: this.itemHttpMapper.toProjection(result.getValue()) })
|
||||
|
||||
@@ -62,7 +62,7 @@ export class HomeServerSharedVaultInvitesController extends BaseHttpController {
|
||||
const result = await this.inviteUserToSharedVaultUseCase.execute({
|
||||
sharedVaultUuid: request.params.sharedVaultUuid,
|
||||
senderUuid: response.locals.user.uuid,
|
||||
recipientUuid: request.body.recipient_uid,
|
||||
recipientUuid: request.body.recipient_uuid,
|
||||
encryptedMessage: request.body.encrypted_message,
|
||||
permission: request.body.permission,
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ExtendedIntegrityPayload } from '../../Domain/Item/ExtendedIntegrityPay
|
||||
import { TypeORMItem } from './TypeORMItem'
|
||||
import { KeySystemAssociationRepositoryInterface } from '../../Domain/KeySystem/KeySystemAssociationRepositoryInterface'
|
||||
import { SharedVaultAssociationRepositoryInterface } from '../../Domain/SharedVault/SharedVaultAssociationRepositoryInterface'
|
||||
import { TypeORMSharedVaultAssociation } from './TypeORMSharedVaultAssociation'
|
||||
|
||||
export class TypeORMItemRepository implements ItemRepositoryInterface {
|
||||
constructor(
|
||||
@@ -92,15 +93,7 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
|
||||
|
||||
const item = this.mapper.toDomain(persistence)
|
||||
|
||||
const keySystemAssociation = await this.keySystemAssociationRepository.findByItemUuid(uuid)
|
||||
if (keySystemAssociation) {
|
||||
item.props.keySystemAssociation = keySystemAssociation
|
||||
}
|
||||
|
||||
const sharedVaultAssociation = await this.sharedVaultAssociationRepository.findByItemUuid(uuid)
|
||||
if (sharedVaultAssociation) {
|
||||
item.props.sharedVaultAssociation = sharedVaultAssociation
|
||||
}
|
||||
await this.decorateItemWithAssociations(item)
|
||||
|
||||
return item
|
||||
}
|
||||
@@ -142,13 +135,21 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.mapper.toDomain(persistence)
|
||||
const item = this.mapper.toDomain(persistence)
|
||||
|
||||
await this.decorateItemWithAssociations(item)
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
async findAll(query: ItemQuery): Promise<Item[]> {
|
||||
const persistence = await this.createFindAllQueryBuilder(query).getMany()
|
||||
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
const domainItems = persistence.map((p) => this.mapper.toDomain(p))
|
||||
|
||||
await Promise.all(domainItems.map((item) => this.decorateItemWithAssociations(item)))
|
||||
|
||||
return domainItems
|
||||
}
|
||||
|
||||
async findAllRaw<T>(query: ItemQuery): Promise<T[]> {
|
||||
@@ -187,12 +188,37 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
|
||||
queryBuilder.orderBy(`item.${query.sortBy}`, query.sortOrder)
|
||||
}
|
||||
|
||||
if (query.includeSharedVaultUuids !== undefined && query.includeSharedVaultUuids.length > 0) {
|
||||
queryBuilder
|
||||
.leftJoin(
|
||||
TypeORMSharedVaultAssociation,
|
||||
'sharedVaultAssociation',
|
||||
'sharedVaultAssociation.itemUuid = item.uuid',
|
||||
)
|
||||
.where('sharedVaultAssociation.sharedVaultUuid IN (:...sharedVaultUuids)', {
|
||||
sharedVaultUuids: query.includeSharedVaultUuids,
|
||||
})
|
||||
|
||||
if (query.userUuid) {
|
||||
queryBuilder.orWhere('item.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
}
|
||||
} else if (query.exclusiveSharedVaultUuids !== undefined && query.exclusiveSharedVaultUuids.length > 0) {
|
||||
queryBuilder
|
||||
.innerJoin(
|
||||
TypeORMSharedVaultAssociation,
|
||||
'sharedVaultAssociation',
|
||||
'sharedVaultAssociation.itemUuid = item.uuid',
|
||||
)
|
||||
.where('sharedVaultAssociation.sharedVaultUuid IN (:...sharedVaultUuids)', {
|
||||
sharedVaultUuids: query.exclusiveSharedVaultUuids,
|
||||
})
|
||||
} else if (query.userUuid !== undefined) {
|
||||
queryBuilder.where('item.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
}
|
||||
|
||||
if (query.selectString !== undefined) {
|
||||
queryBuilder.select(query.selectString)
|
||||
}
|
||||
if (query.userUuid !== undefined) {
|
||||
queryBuilder.where('item.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
}
|
||||
if (query.uuids && query.uuids.length > 0) {
|
||||
queryBuilder.andWhere('item.uuid IN (:...uuids)', { uuids: query.uuids })
|
||||
}
|
||||
@@ -226,4 +252,25 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
|
||||
|
||||
return queryBuilder
|
||||
}
|
||||
|
||||
private async decorateItemWithAssociations(item: Item): Promise<void> {
|
||||
await Promise.all([
|
||||
this.decorateItemWithKeySystemAssociation(item),
|
||||
this.decorateItemWithSharedVaultAssociation(item),
|
||||
])
|
||||
}
|
||||
|
||||
private async decorateItemWithKeySystemAssociation(item: Item): Promise<void> {
|
||||
const keySystemAssociation = await this.keySystemAssociationRepository.findByItemUuid(item.uuid)
|
||||
if (keySystemAssociation) {
|
||||
item.props.keySystemAssociation = keySystemAssociation
|
||||
}
|
||||
}
|
||||
|
||||
private async decorateItemWithSharedVaultAssociation(item: Item): Promise<void> {
|
||||
const sharedVaultAssociation = await this.sharedVaultAssociationRepository.findByItemUuid(item.uuid)
|
||||
if (sharedVaultAssociation) {
|
||||
item.props.sharedVaultAssociation = sharedVaultAssociation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,20 @@ export class TypeORMMessageRepository implements MessageRepositoryInterface {
|
||||
private mapper: MapperInterface<Message, TypeORMMessage>,
|
||||
) {}
|
||||
|
||||
async findByRecipientUuidUpdatedAfter(uuid: Uuid, updatedAtTimestamp: number): Promise<Message[]> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('message')
|
||||
.where('message.recipient_uuid = :recipientUuid', {
|
||||
recipientUuid: uuid.value,
|
||||
})
|
||||
.andWhere('message.updated_at_timestamp > :updatedAtTimestamp', {
|
||||
updatedAtTimestamp,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
}
|
||||
|
||||
async findByRecipientUuid(uuid: Uuid): Promise<Message[]> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('message')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Repository } from 'typeorm'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { NotificationRepositoryInterface } from '../../Domain/Notifications/NotificationRepositoryInterface'
|
||||
import { TypeORMNotification } from './TypeORMNotification'
|
||||
@@ -16,4 +16,29 @@ export class TypeORMNotificationRepository implements NotificationRepositoryInte
|
||||
|
||||
await this.ormRepository.save(persistence)
|
||||
}
|
||||
|
||||
async findByUserUuidUpdatedAfter(uuid: Uuid, updatedAtTimestamp: number): Promise<Notification[]> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('notification')
|
||||
.where('notification.user_uuid = :userUuid', {
|
||||
userUuid: uuid.value,
|
||||
})
|
||||
.andWhere('notification.updated_at_timestamp > :updatedAtTimestamp', {
|
||||
updatedAtTimestamp,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
}
|
||||
|
||||
async findByUserUuid(uuid: Uuid): Promise<Notification[]> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('notification')
|
||||
.where('notification.user_uuid = :userUuid', {
|
||||
userUuid: uuid.value,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,20 @@ export class TypeORMSharedVaultInviteRepository implements SharedVaultInviteRepo
|
||||
private mapper: MapperInterface<SharedVaultInvite, TypeORMSharedVaultInvite>,
|
||||
) {}
|
||||
|
||||
async findByUserUuidUpdatedAfter(userUuid: Uuid, updatedAtTimestamp: number): Promise<SharedVaultInvite[]> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault_invite')
|
||||
.where('shared_vault_invite.user_uuid = :userUuid', {
|
||||
userUuid: userUuid.value,
|
||||
})
|
||||
.andWhere('shared_vault_invite.updated_at_timestamp > :updatedAtTimestamp', {
|
||||
updatedAtTimestamp,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
}
|
||||
|
||||
async findBySenderUuidAndSharedVaultUuid(dto: {
|
||||
senderUuid: Uuid
|
||||
sharedVaultUuid: Uuid
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
|
||||
import { Notification } from '../../Domain/Notifications/Notification'
|
||||
import { NotificationHttpRepresentation } from './NotificationHttpRepresentation'
|
||||
|
||||
export class NotificationHttpMapper implements MapperInterface<Notification, NotificationHttpRepresentation> {
|
||||
toDomain(_projection: NotificationHttpRepresentation): Notification {
|
||||
throw new Error('Mapping from http representation to domain is not implemented.')
|
||||
}
|
||||
|
||||
toProjection(domain: Notification): NotificationHttpRepresentation {
|
||||
return {
|
||||
uuid: domain.id.toString(),
|
||||
user_uuid: domain.props.userUuid.value,
|
||||
type: domain.props.type.value,
|
||||
payload: domain.props.payload.toString(),
|
||||
created_at_timestamp: domain.props.timestamps.createdAt,
|
||||
updated_at_timestamp: domain.props.timestamps.updatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface NotificationHttpRepresentation {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
payload: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export class SharedVaultInviteHttpMapper
|
||||
user_uuid: domain.props.userUuid.value,
|
||||
sender_uuid: domain.props.senderUuid.value,
|
||||
encrypted_message: domain.props.encryptedMessage,
|
||||
permissions: domain.props.permission.value,
|
||||
permission: domain.props.permission.value,
|
||||
created_at_timestamp: domain.props.timestamps.createdAt,
|
||||
updated_at_timestamp: domain.props.timestamps.updatedAt,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface SharedVaultInviteHttpRepresentation {
|
||||
user_uuid: string
|
||||
sender_uuid: string
|
||||
encrypted_message: string
|
||||
permissions: string
|
||||
permission: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
|
||||
@@ -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 { TypeORMNotification } from '../../Infra/TypeORM/TypeORMNotification'
|
||||
import { NotificationType } from '../../Domain/Notifications/NotificationType'
|
||||
|
||||
export class NotificationPersistenceMapper implements MapperInterface<Notification, TypeORMNotification> {
|
||||
toDomain(projection: TypeORMNotification): Notification {
|
||||
@@ -25,10 +31,16 @@ export class NotificationPersistenceMapper implements MapperInterface<Notificati
|
||||
}
|
||||
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(
|
||||
{
|
||||
userUuid,
|
||||
payload: projection.payload,
|
||||
payload,
|
||||
type,
|
||||
timestamps,
|
||||
},
|
||||
@@ -47,7 +59,7 @@ export class NotificationPersistenceMapper implements MapperInterface<Notificati
|
||||
|
||||
typeorm.uuid = domain.id.toString()
|
||||
typeorm.userUuid = domain.props.userUuid.value
|
||||
typeorm.payload = domain.props.payload
|
||||
typeorm.payload = domain.props.payload.toString()
|
||||
typeorm.type = domain.props.type.value
|
||||
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
|
||||
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.10.4](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.3...@standardnotes/websockets-server@1.10.4) (2023-07-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.10.3](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.2...@standardnotes/websockets-server@1.10.3) (2023-07-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.10.3",
|
||||
"version": "1.10.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user