mirror of
https://github.com/standardnotes/server
synced 2026-07-14 00:01:54 -04:00
fix: unify use case usage (#654)
This commit is contained in:
+71
-80
@@ -42,95 +42,86 @@ describe('CheckIntegrity', () => {
|
||||
})
|
||||
|
||||
it('should return an empty result if there are no integrity mismatches', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
freeUser: false,
|
||||
integrityPayloads: [
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
updated_at_timestamp: 2,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
updated_at_timestamp: 3,
|
||||
},
|
||||
{
|
||||
uuid: '5-6-7',
|
||||
updated_at_timestamp: 5,
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
mismatches: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('should return a mismatch item that has a different update at timemstap', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
freeUser: false,
|
||||
integrityPayloads: [
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
updated_at_timestamp: 3,
|
||||
},
|
||||
{
|
||||
uuid: '5-6-7',
|
||||
updated_at_timestamp: 5,
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
mismatches: [
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
freeUser: false,
|
||||
integrityPayloads: [
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
updated_at_timestamp: 2,
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('should return a mismatch item that is missing on the client side', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
freeUser: false,
|
||||
integrityPayloads: [
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
updated_at_timestamp: 2,
|
||||
},
|
||||
{
|
||||
uuid: '5-6-7',
|
||||
updated_at_timestamp: 5,
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual({
|
||||
mismatches: [
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
updated_at_timestamp: 3,
|
||||
},
|
||||
{
|
||||
uuid: '5-6-7',
|
||||
updated_at_timestamp: 5,
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(result.getValue()).toEqual([])
|
||||
})
|
||||
|
||||
it('should return a mismatch item that has a different update at timemstap', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
freeUser: false,
|
||||
integrityPayloads: [
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
updated_at_timestamp: 3,
|
||||
},
|
||||
{
|
||||
uuid: '5-6-7',
|
||||
updated_at_timestamp: 5,
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(result.getValue()).toEqual([
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
updated_at_timestamp: 2,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should return a mismatch item that is missing on the client side', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
freeUser: false,
|
||||
integrityPayloads: [
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
updated_at_timestamp: 1,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
updated_at_timestamp: 2,
|
||||
},
|
||||
{
|
||||
uuid: '5-6-7',
|
||||
updated_at_timestamp: 5,
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(result.getValue()).toEqual([
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
updated_at_timestamp: 3,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { IntegrityPayload } from '@standardnotes/responses'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
|
||||
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
|
||||
import { UseCaseInterface } from '../../UseCaseInterface'
|
||||
import { CheckIntegrityDTO } from './CheckIntegrityDTO'
|
||||
import { CheckIntegrityResponse } from './CheckIntegrityResponse'
|
||||
import { ExtendedIntegrityPayload } from '../../../Item/ExtendedIntegrityPayload'
|
||||
|
||||
export class CheckIntegrity implements UseCaseInterface {
|
||||
export class CheckIntegrity implements UseCaseInterface<IntegrityPayload[]> {
|
||||
constructor(private itemRepository: ItemRepositoryInterface) {}
|
||||
|
||||
async execute(dto: CheckIntegrityDTO): Promise<CheckIntegrityResponse> {
|
||||
async execute(dto: CheckIntegrityDTO): Promise<Result<IntegrityPayload[]>> {
|
||||
const serverItemIntegrityPayloads = await this.itemRepository.findItemsForComputingIntegrityPayloads(dto.userUuid)
|
||||
|
||||
const serverItemIntegrityPayloadsMap = new Map<string, ExtendedIntegrityPayload>()
|
||||
@@ -59,8 +58,6 @@ export class CheckIntegrity implements UseCaseInterface {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mismatches,
|
||||
}
|
||||
return Result.ok(mismatches)
|
||||
}
|
||||
}
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
import { IntegrityPayload } from '@standardnotes/responses'
|
||||
|
||||
export type CheckIntegrityResponse = {
|
||||
mismatches: IntegrityPayload[]
|
||||
}
|
||||
@@ -15,23 +15,23 @@ describe('GetItem', () => {
|
||||
})
|
||||
|
||||
it('should fail if item is not found', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemUuid: '2-3-4',
|
||||
}),
|
||||
).toEqual({ success: false, message: 'Could not find item with uuid 2-3-4' })
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemUuid: '2-3-4',
|
||||
})
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('Could not find item with uuid 2-3-4')
|
||||
})
|
||||
|
||||
it('should succeed if item is found', async () => {
|
||||
const item = {} as jest.Mocked<Item>
|
||||
itemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(item)
|
||||
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemUuid: '2-3-4',
|
||||
}),
|
||||
).toEqual({ success: true, item })
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemUuid: '2-3-4',
|
||||
})
|
||||
|
||||
expect(result.getValue()).toEqual(item)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
|
||||
import { UseCaseInterface } from '../../UseCaseInterface'
|
||||
import { GetItemDTO } from './GetItemDTO'
|
||||
import { GetItemResponse } from './GetItemResponse'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class GetItem implements UseCaseInterface {
|
||||
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
|
||||
import { GetItemDTO } from './GetItemDTO'
|
||||
import { Item } from '../../../Item/Item'
|
||||
|
||||
export class GetItem implements UseCaseInterface<Item> {
|
||||
constructor(private itemRepository: ItemRepositoryInterface) {}
|
||||
|
||||
async execute(dto: GetItemDTO): Promise<GetItemResponse> {
|
||||
async execute(dto: GetItemDTO): Promise<Result<Item>> {
|
||||
const item = await this.itemRepository.findByUuidAndUserUuid(dto.itemUuid, dto.userUuid)
|
||||
|
||||
if (item === null) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Could not find item with uuid ${dto.itemUuid}`,
|
||||
}
|
||||
return Result.fail(`Could not find item with uuid ${dto.itemUuid}`)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
item,
|
||||
}
|
||||
return Result.ok(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Item } from '../../../Item/Item'
|
||||
|
||||
export type GetItemResponse =
|
||||
| {
|
||||
success: true
|
||||
item: Item
|
||||
}
|
||||
| {
|
||||
success: false
|
||||
message: string
|
||||
}
|
||||
@@ -54,20 +54,20 @@ describe('SyncItems', () => {
|
||||
})
|
||||
|
||||
it('should sync items', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
cursorToken: 'bar',
|
||||
limit: 10,
|
||||
readOnlyAccess: false,
|
||||
contentType: 'Note',
|
||||
apiVersion: ApiVersion.v20200115,
|
||||
sessionUuid: null,
|
||||
}),
|
||||
).toEqual({
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
syncToken: 'foo',
|
||||
cursorToken: 'bar',
|
||||
limit: 10,
|
||||
readOnlyAccess: false,
|
||||
contentType: 'Note',
|
||||
apiVersion: ApiVersion.v20200115,
|
||||
sessionUuid: null,
|
||||
snjsVersion: '1.2.3',
|
||||
})
|
||||
expect(result.getValue()).toEqual({
|
||||
conflicts: [],
|
||||
cursorToken: 'asdzxc',
|
||||
retrievedItems: [item1],
|
||||
@@ -93,18 +93,18 @@ describe('SyncItems', () => {
|
||||
})
|
||||
|
||||
it('should sync items and return items keys on top for first sync', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
limit: 10,
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: '2-3-4',
|
||||
contentType: 'Note',
|
||||
apiVersion: ApiVersion.v20200115,
|
||||
}),
|
||||
).toEqual({
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '1-2-3',
|
||||
itemHashes: [itemHash],
|
||||
computeIntegrityHash: false,
|
||||
limit: 10,
|
||||
readOnlyAccess: false,
|
||||
sessionUuid: '2-3-4',
|
||||
contentType: 'Note',
|
||||
apiVersion: ApiVersion.v20200115,
|
||||
snjsVersion: '1.2.3',
|
||||
})
|
||||
expect(result.getValue()).toEqual({
|
||||
conflicts: [],
|
||||
cursorToken: 'asdzxc',
|
||||
retrievedItems: [item3, item1],
|
||||
@@ -134,20 +134,21 @@ describe('SyncItems', () => {
|
||||
syncToken: 'qwerty',
|
||||
})
|
||||
|
||||
expect(
|
||||
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,
|
||||
}),
|
||||
).toEqual({
|
||||
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.getValue()).toEqual({
|
||||
conflicts: [
|
||||
{
|
||||
serverItem: item2,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { Item } from '../../../Item/Item'
|
||||
import { ItemConflict } from '../../../Item/ItemConflict'
|
||||
import { ItemServiceInterface } from '../../../Item/ItemServiceInterface'
|
||||
import { UseCaseInterface } from '../../UseCaseInterface'
|
||||
import { SyncItemsDTO } from './SyncItemsDTO'
|
||||
import { SyncItemsResponse } from './SyncItemsResponse'
|
||||
|
||||
export class SyncItems implements UseCaseInterface {
|
||||
export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
|
||||
constructor(private itemService: ItemServiceInterface) {}
|
||||
|
||||
async execute(dto: SyncItemsDTO): Promise<SyncItemsResponse> {
|
||||
async execute(dto: SyncItemsDTO): Promise<Result<SyncItemsResponse>> {
|
||||
const getItemsResult = await this.itemService.getItems({
|
||||
userUuid: dto.userUuid,
|
||||
syncToken: dto.syncToken,
|
||||
@@ -38,7 +38,7 @@ export class SyncItems implements UseCaseInterface {
|
||||
cursorToken: getItemsResult.cursorToken,
|
||||
}
|
||||
|
||||
return syncResponse
|
||||
return Result.ok(syncResponse)
|
||||
}
|
||||
|
||||
private isFirstSync(dto: SyncItemsDTO): boolean {
|
||||
|
||||
@@ -5,10 +5,12 @@ export type SyncItemsDTO = {
|
||||
itemHashes: Array<ItemHash>
|
||||
computeIntegrityHash: boolean
|
||||
limit: number
|
||||
sharedVaultUuids?: string[] | null
|
||||
syncToken?: string | null
|
||||
cursorToken?: string | null
|
||||
contentType?: string
|
||||
apiVersion: string
|
||||
snjsVersion: string
|
||||
readOnlyAccess: boolean
|
||||
sessionUuid: string | null
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export interface UseCaseInterface {
|
||||
execute(...args: any[]): Promise<Record<string, unknown>>
|
||||
}
|
||||
+18
-6
@@ -10,6 +10,7 @@ import { ItemProjection } from '../../../Projection/ItemProjection'
|
||||
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
|
||||
import { ApiVersion } from '../../../Domain/Api/ApiVersion'
|
||||
import { SyncItems } from '../../../Domain/UseCase/Syncing/SyncItems/SyncItems'
|
||||
import { HttpStatusCode } from '@standardnotes/responses'
|
||||
|
||||
export class HomeServerItemsController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -41,16 +42,21 @@ export class HomeServerItemsController extends BaseHttpController {
|
||||
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,
|
||||
})
|
||||
if (syncResult.isFailed()) {
|
||||
return this.json({ error: { message: syncResult.getError() } }, HttpStatusCode.BadRequest)
|
||||
}
|
||||
|
||||
const syncResponse = await this.syncResponseFactoryResolver
|
||||
.resolveSyncResponseFactoryVersion(request.body.api)
|
||||
.createResponse(syncResult)
|
||||
.createResponse(syncResult.getValue())
|
||||
|
||||
return this.json(syncResponse)
|
||||
}
|
||||
@@ -67,19 +73,25 @@ export class HomeServerItemsController extends BaseHttpController {
|
||||
freeUser: response.locals.freeUser,
|
||||
})
|
||||
|
||||
return this.json(result)
|
||||
if (result.isFailed()) {
|
||||
return this.json({ error: { message: result.getError() } }, HttpStatusCode.BadRequest)
|
||||
}
|
||||
|
||||
return this.json({
|
||||
mismatches: result.getValue(),
|
||||
})
|
||||
}
|
||||
|
||||
async getSingleItem(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
async getSingleItem(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.getItem.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
itemUuid: request.params.uuid,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
return this.notFound()
|
||||
if (result.isFailed()) {
|
||||
return this.json({ error: { message: result.getError() } }, 404)
|
||||
}
|
||||
|
||||
return this.json({ item: await this.itemProjector.projectFull(result.item) })
|
||||
return this.json({ item: await this.itemProjector.projectFull(result.getValue()) })
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -2,9 +2,10 @@ import 'reflect-metadata'
|
||||
|
||||
import * as express from 'express'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { results } from 'inversify-express-utils'
|
||||
|
||||
import { InversifyExpressItemsController } from './InversifyExpressItemsController'
|
||||
import { results } from 'inversify-express-utils'
|
||||
import { Item } from '../../Domain/Item/Item'
|
||||
import { ItemProjection } from '../../Projection/ItemProjection'
|
||||
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
||||
@@ -35,13 +36,13 @@ describe('InversifyExpressItemsController', () => {
|
||||
itemProjector.projectFull = jest.fn().mockReturnValue({ foo: 'bar' })
|
||||
|
||||
syncItems = {} as jest.Mocked<SyncItems>
|
||||
syncItems.execute = jest.fn().mockReturnValue({ foo: 'bar' })
|
||||
syncItems.execute = jest.fn().mockReturnValue(Result.ok({ foo: 'bar' }))
|
||||
|
||||
checkIntegrity = {} as jest.Mocked<CheckIntegrity>
|
||||
checkIntegrity.execute = jest.fn().mockReturnValue({ mismatches: [{ uuid: '1-2-3', updated_at_timestamp: 2 }] })
|
||||
checkIntegrity.execute = jest.fn().mockReturnValue(Result.ok([{ uuid: '1-2-3', updated_at_timestamp: 2 }]))
|
||||
|
||||
getItem = {} as jest.Mocked<GetItem>
|
||||
getItem.execute = jest.fn().mockReturnValue({ success: true, item: {} as jest.Mocked<Item> })
|
||||
getItem.execute = jest.fn().mockReturnValue(Result.ok({} as jest.Mocked<Item>))
|
||||
|
||||
request = {
|
||||
headers: {},
|
||||
@@ -100,7 +101,7 @@ describe('InversifyExpressItemsController', () => {
|
||||
|
||||
it('should return 404 on a missing single item', async () => {
|
||||
request.params.uuid = '1-2-3'
|
||||
getItem.execute = jest.fn().mockReturnValue({ success: false })
|
||||
getItem.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
const httpResponse = <results.NotFoundResult>await createController().getSingleItem(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
+1
-4
@@ -36,10 +36,7 @@ export class InversifyExpressItemsController extends HomeServerItemsController {
|
||||
}
|
||||
|
||||
@httpGet('/:uuid')
|
||||
override async getSingleItem(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
override async getSingleItem(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
return super.getSingleItem(request, response)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user