Compare commits

..

22 Commits

Author SHA1 Message Date
standardci
b9032f3012 chore(release): publish new version
- @standardnotes/syncing-server@1.26.3
2022-12-15 06:31:07 +00:00
Karol Sójko
ce53c459e6 fix(syncing-server): select fields in query for revisions 2022-12-15 07:28:43 +01:00
standardci
6df42fb0d5 chore(release): publish new version
- @standardnotes/syncing-server@1.26.2
2022-12-14 19:08:28 +00:00
Karol Sójko
1e2b496f4f fix(syncing-server): revisions procedure logs 2022-12-14 20:06:07 +01:00
standardci
528c1b0d57 chore(release): publish new version
- @standardnotes/syncing-server@1.26.1
2022-12-14 18:58:11 +00:00
Karol Sójko
22fba8ba80 fix(syncing-server): revisions procedure logs 2022-12-14 19:56:17 +01:00
standardci
6f26261ebe chore(release): publish new version
- @standardnotes/syncing-server@1.26.0
2022-12-14 15:51:14 +00:00
Karol Sójko
4b1fe3ba91 feat(syncing-server): change revisions procedure to pagination instead of streaming 2022-12-14 16:49:21 +01:00
standardci
9f95262bd4 chore(release): publish new version
- @standardnotes/syncing-server@1.25.6
2022-12-14 09:33:24 +00:00
Karol Sójko
2ec28e541e fix(syncing-server): additional stream events handling on revisions procedure 2022-12-14 10:31:06 +01:00
standardci
4764d4b19a chore(release): publish new version
- @standardnotes/syncing-server@1.25.5
2022-12-14 09:00:42 +00:00
Karol Sójko
9b27547dae fix(syncing-server): revisions procedure with env var defined ranges 2022-12-14 09:58:41 +01:00
standardci
a96f2c9153 chore(release): publish new version
- @standardnotes/syncing-server@1.25.4
2022-12-13 11:14:35 +00:00
Karol Sójko
225e0aaf88 fix(syncing-server): logs on revisions procedure 2022-12-13 12:12:42 +01:00
standardci
f0c85910bc chore(release): publish new version
- @standardnotes/syncing-server@1.25.3
2022-12-13 11:07:23 +00:00
Karol Sójko
124c443528 fix(syncing-server): revisions ownership procedure destructured 2022-12-13 12:05:10 +01:00
standardci
37c7f8d39f chore(release): publish new version
- @standardnotes/syncing-server@1.25.2
2022-12-13 07:19:55 +00:00
Karol Sójko
c419f1ce22 fix(syncing-server): change revisions migration to notes 2022-12-13 08:17:55 +01:00
standardci
4949cdfe2f chore(release): publish new version
- @standardnotes/syncing-server@1.25.1
2022-12-13 06:03:06 +00:00
Karol Sójko
cd101b96ea fix(syncing-server): revisions procedure properties 2022-12-13 07:01:06 +01:00
standardci
40d0e4631f chore(release): publish new version
- @standardnotes/syncing-server@1.25.0
2022-12-12 19:06:24 +00:00
Karol Sójko
a55a995660 feat(syncing-server): fix streaming items for revisions update 2022-12-12 20:03:45 +01:00
7 changed files with 152 additions and 448 deletions

View File

@@ -3,6 +3,72 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.2...@standardnotes/syncing-server@1.26.3) (2022-12-15)
### Bug Fixes
* **syncing-server:** select fields in query for revisions ([ce53c45](https://github.com/standardnotes/syncing-server-js/commit/ce53c459e6ad0d469fcd0ebd7bf4caeb0e1d9c9c))
## [1.26.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.1...@standardnotes/syncing-server@1.26.2) (2022-12-14)
### Bug Fixes
* **syncing-server:** revisions procedure logs ([1e2b496](https://github.com/standardnotes/syncing-server-js/commit/1e2b496f4f87fd49ae8fba8ed9b76d3b6a2c31fa))
## [1.26.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.0...@standardnotes/syncing-server@1.26.1) (2022-12-14)
### Bug Fixes
* **syncing-server:** revisions procedure logs ([22fba8b](https://github.com/standardnotes/syncing-server-js/commit/22fba8ba806115b0f4bb4b083ae8595a3f0010b0))
# [1.26.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.6...@standardnotes/syncing-server@1.26.0) (2022-12-14)
### Features
* **syncing-server:** change revisions procedure to pagination instead of streaming ([4b1fe3b](https://github.com/standardnotes/syncing-server-js/commit/4b1fe3ba91594858e15cbdfbc21062c428dd03b4))
## [1.25.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.5...@standardnotes/syncing-server@1.25.6) (2022-12-14)
### Bug Fixes
* **syncing-server:** additional stream events handling on revisions procedure ([2ec28e5](https://github.com/standardnotes/syncing-server-js/commit/2ec28e541efa2bd9172431d45c5c1560692a912c))
## [1.25.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.4...@standardnotes/syncing-server@1.25.5) (2022-12-14)
### Bug Fixes
* **syncing-server:** revisions procedure with env var defined ranges ([9b27547](https://github.com/standardnotes/syncing-server-js/commit/9b27547dae1e5d5e6d071a069803e2bf3f8acdda))
## [1.25.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.3...@standardnotes/syncing-server@1.25.4) (2022-12-13)
### Bug Fixes
* **syncing-server:** logs on revisions procedure ([225e0aa](https://github.com/standardnotes/syncing-server-js/commit/225e0aaf88a396bf308c2e5eed0bb6e130cb2d64))
## [1.25.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.2...@standardnotes/syncing-server@1.25.3) (2022-12-13)
### Bug Fixes
* **syncing-server:** revisions ownership procedure destructured ([124c443](https://github.com/standardnotes/syncing-server-js/commit/124c4435285c2c2e8d0ce8b47907ebd47af27576))
## [1.25.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.1...@standardnotes/syncing-server@1.25.2) (2022-12-13)
### Bug Fixes
* **syncing-server:** change revisions migration to notes ([c419f1c](https://github.com/standardnotes/syncing-server-js/commit/c419f1ce220c27acabfc813a30b3edd6c4aadaa1))
## [1.25.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.0...@standardnotes/syncing-server@1.25.1) (2022-12-13)
### Bug Fixes
* **syncing-server:** revisions procedure properties ([cd101b9](https://github.com/standardnotes/syncing-server-js/commit/cd101b96eae8969a4dd2387deb1d4e8679ead216))
# [1.25.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.7...@standardnotes/syncing-server@1.25.0) (2022-12-12)
### Features
* **syncing-server:** fix streaming items for revisions update ([a55a995](https://github.com/standardnotes/syncing-server-js/commit/a55a9956602bee7dbb0f93f058aceff7a2136ffd))
## [1.24.7](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.6...@standardnotes/syncing-server@1.24.7) (2022-12-12)
### Bug Fixes

View File

@@ -10,51 +10,69 @@ import { Env } from '../src/Bootstrap/Env'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { ItemRepositoryInterface } from '../src/Domain/Item/ItemRepositoryInterface'
import { Stream } from 'stream'
import { ContentType } from '@standardnotes/common'
const fixRevisionsOwnership = async (
year: number,
month: number,
itemRepository: ItemRepositoryInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
logger: Logger,
): Promise<void> => {
const stream = await itemRepository.streamAll({
sortBy: 'updated_at_timestamp',
const createdAfter = new Date(`${year}-${month}-1`)
const createdBefore = new Date(`${month !== 12 ? year : year + 1}-${month !== 12 ? month + 1 : 1}-1`)
logger.info(`Processing items between ${createdAfter.toISOString()} and ${createdBefore.toISOString()}`)
const itemsCount = await itemRepository.countAll({
createdBetween: [createdAfter, createdBefore],
selectFields: ['uuid', 'userUuid'],
contentType: [ContentType.Note, ContentType.File],
sortOrder: 'ASC',
createdBefore: new Date('2022-11-23'),
selectFields: ['user_uuid', 'uuid'],
sortBy: 'uuid',
})
return new Promise((resolve, reject) => {
stream
.pipe(
new Stream.Transform({
objectMode: true,
transform: async (rawItemData, _encoding, callback) => {
try {
if (!rawItemData.item_user_uuid || !rawItemData.item_uuid) {
logger.error('Could not process item %O', rawItemData)
logger.info(`There are ${itemsCount} items between ${createdAfter.toISOString()} and ${createdBefore.toISOString()}`)
return callback()
}
const limit = 500
const amountOfPages = Math.ceil(itemsCount / limit)
const tenPercentOfPages = Math.ceil(amountOfPages / 10)
let itemsProcessedCounter = 0
let itemsSkippedCounter = 0
for (let page = 1; page <= amountOfPages; page++) {
if (page % tenPercentOfPages === 0) {
logger.info(
`Processing page ${page} of ${amountOfPages} items between ${createdAfter.toISOString()} and ${createdBefore.toISOString()}. Processed successfully ${itemsProcessedCounter} items. Skipped ${itemsSkippedCounter} items.`,
)
}
await domainEventPublisher.publish(
domainEventFactory.createRevisionsOwnershipUpdateRequestedEvent({
userUuid: rawItemData.item_user_uuid,
itemUuid: rawItemData.item_uuid,
}),
)
} catch (error) {
logger.error(`Could not process item ${rawItemData.item_uuid}: ${(error as Error).message}`)
}
const items = await itemRepository.findAll({
createdBetween: [createdAfter, createdBefore],
selectFields: ['uuid', 'userUuid'],
contentType: [ContentType.Note, ContentType.File],
offset: (page - 1) * limit,
limit,
sortOrder: 'ASC',
sortBy: 'uuid',
})
callback()
},
for (const item of items) {
if (!item.userUuid || !item.uuid) {
itemsSkippedCounter++
continue
}
await domainEventPublisher.publish(
domainEventFactory.createRevisionsOwnershipUpdateRequestedEvent({
userUuid: item.userUuid,
itemUuid: item.uuid,
}),
)
.on('finish', resolve)
.on('error', reject)
})
itemsProcessedCounter++
}
}
}
const container = new ContainerConfigLoader()
@@ -70,7 +88,19 @@ void container.load().then((container) => {
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
Promise.resolve(fixRevisionsOwnership(itemRepository, domainEventFactory, domainEventPublisher, logger))
const years = env.get('REVISION_YEARS').split(',')
const months = env.get('REVISION_MONTHS').split(',')
const promises = []
for (const year of years) {
for (const month of months) {
promises.push(
fixRevisionsOwnership(+year, +month, itemRepository, domainEventFactory, domainEventPublisher, logger),
)
}
}
Promise.all(promises)
.then(() => {
logger.info('revisions ownership fix complete.')

View File

@@ -5,33 +5,33 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-local')
echo "Starting Web in Local Mode..."
echo "[Docker] Starting Web in Local Mode..."
yarn workspace @standardnotes/syncing-server start:local
;;
'start-web' )
echo "Starting Web..."
echo "[Docker] Starting Web..."
yarn workspace @standardnotes/syncing-server start
;;
'start-worker' )
echo "Starting Worker..."
echo "[Docker] Starting Worker..."
yarn workspace @standardnotes/syncing-server worker
;;
'content-size-recalculate' )
echo "Starting Content Size Recalculation..."
echo "[Docker] Starting Content Size Recalculation..."
USER_UUID=$1 && shift 1
yarn workspace @standardnotes/syncing-server content-size $USER_UUID
;;
'revisions-ownership-fix' )
echo "Starting Revisions Ownership Fixing..."
echo "[Docker] Starting Revisions Ownership Fixing..."
yarn workspace @standardnotes/syncing-server revisions-ownership
;;
* )
echo "Unknown command"
echo "[Docker] Unknown command"
;;
esac

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.24.7",
"version": "1.26.3",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -1,14 +1,14 @@
export type ItemQuery = {
userUuid?: string
sortBy: string
sortOrder: 'ASC' | 'DESC'
sortBy?: string
sortOrder?: 'ASC' | 'DESC'
uuids?: Array<string>
lastSyncTime?: number
syncTimeComparison?: '>' | '>='
contentType?: string
contentType?: string | string[]
deleted?: boolean
offset?: number
limit?: number
createdBefore?: Date
createdBetween?: Date[]
selectFields?: string[]
}

View File

@@ -1,401 +0,0 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { ContentType } from '@standardnotes/common'
import { Item } from '../../Domain/Item/Item'
import { MySQLItemRepository } from './MySQLItemRepository'
import { TimerInterface } from '@standardnotes/time'
import { ReadStream } from 'fs'
describe('MySQLItemRepository', () => {
let queryBuilder: SelectQueryBuilder<Item>
let ormRepository: Repository<Item>
let item: Item
let timer: TimerInterface
const createRepository = () => new MySQLItemRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<Item>>
item = {} as jest.Mocked<Item>
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn(() => 1616161616161616)
ormRepository = {} as jest.Mocked<Repository<Item>>
ormRepository.save = jest.fn()
ormRepository.remove = jest.fn()
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
})
it('should save', async () => {
await createRepository().save(item)
expect(ormRepository.save).toHaveBeenCalledWith(item)
})
it('should remove', async () => {
await createRepository().remove(item)
expect(ormRepository.remove).toHaveBeenCalledWith(item)
})
it('should delete all items for a given user', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.delete = jest.fn().mockReturnThis()
queryBuilder.from = jest.fn().mockReturnThis()
queryBuilder.execute = jest.fn()
await createRepository().deleteByUserUuid('123')
expect(queryBuilder.delete).toHaveBeenCalled()
expect(queryBuilder.from).toHaveBeenCalledWith('items')
expect(queryBuilder.where).toHaveBeenCalledWith('user_uuid = :userUuid', { userUuid: '123' })
expect(queryBuilder.execute).toHaveBeenCalled()
})
it('should find one item by uuid and user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(item)
const result = await createRepository().findByUuidAndUserUuid('1-2-3', '2-3-4')
expect(queryBuilder.where).toHaveBeenCalledWith('item.uuid = :uuid AND item.user_uuid = :userUuid', {
uuid: '1-2-3',
userUuid: '2-3-4',
})
expect(result).toEqual(item)
})
it('should find one item by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(item)
const result = await createRepository().findByUuid('1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('item.uuid = :uuid', {
uuid: '1-2-3',
})
expect(result).toEqual(item)
})
it('should find items by all query criteria filled in', async () => {
queryBuilder.getMany = jest.fn().mockReturnValue([item])
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().findAll({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual([item])
})
it('should stream items by all query criteria filled in', async () => {
const stream = {} as jest.Mocked<ReadStream>
queryBuilder.stream = jest.fn().mockReturnValue(stream)
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().streamAll({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual(stream)
})
it('should find items content sizes by all query criteria filled in', async () => {
queryBuilder.getRawMany = jest.fn().mockReturnValue([{ uuid: item.uuid, contentSize: item.contentSize }])
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.select = jest.fn()
queryBuilder.addSelect = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().findContentSizeForComputingTransferLimit({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.select).toHaveBeenCalledWith('item.uuid', 'uuid')
expect(queryBuilder.addSelect).toHaveBeenCalledWith('item.content_size', 'contentSize')
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual([item])
})
it('should find items by all query criteria filled in', async () => {
queryBuilder.getMany = jest.fn().mockReturnValue([item])
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().findAll({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual([item])
})
it('should count items by all query criteria filled in', async () => {
queryBuilder.getCount = jest.fn().mockReturnValue(1)
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
queryBuilder.orderBy = jest.fn()
queryBuilder.skip = jest.fn()
queryBuilder.take = jest.fn()
const result = await createRepository().countAll({
userUuid: '1-2-3',
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
deleted: false,
contentType: ContentType.Note,
lastSyncTime: 123,
syncTimeComparison: '>=',
uuids: ['2-3-4'],
offset: 1,
limit: 10,
})
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(4)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.uuid IN (:...uuids)', { uuids: ['2-3-4'] })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(2, 'item.deleted = :deleted', { deleted: false })
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(3, 'item.content_type = :contentType', {
contentType: 'Note',
})
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(4, 'item.updated_at_timestamp >= :lastSyncTime', {
lastSyncTime: 123,
})
expect(queryBuilder.skip).toHaveBeenCalledWith(1)
expect(queryBuilder.take).toHaveBeenCalledWith(10)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual(1)
})
it('should find items by only mandatory query criteria', async () => {
queryBuilder.getMany = jest.fn().mockReturnValue([item])
queryBuilder.where = jest.fn()
queryBuilder.orderBy = jest.fn()
const result = await createRepository().findAll({
sortBy: 'updated_at_timestamp',
sortOrder: 'DESC',
})
expect(queryBuilder.orderBy).toHaveBeenCalledWith('item.updated_at_timestamp', 'DESC')
expect(result).toEqual([item])
})
it('should find dates for computing integrity hash', async () => {
queryBuilder.getRawMany = jest
.fn()
.mockReturnValue([{ updated_at_timestamp: 1616164633241312 }, { updated_at_timestamp: 1616164633242313 }])
queryBuilder.select = jest.fn()
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
const result = await createRepository().findDatesForComputingIntegrityHash('1-2-3')
expect(queryBuilder.select).toHaveBeenCalledWith('item.updated_at_timestamp')
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.deleted = :deleted', { deleted: false })
expect(result.length).toEqual(2)
expect(result[0]).toEqual({ updated_at_timestamp: 1616164633242313 })
expect(result[1]).toEqual({ updated_at_timestamp: 1616164633241312 })
})
it('should find items for computing integrity payloads', async () => {
queryBuilder.getRawMany = jest.fn().mockReturnValue([
{ uuid: '1-2-3', updated_at_timestamp: 1616164633241312, content_type: ContentType.Note },
{ uuid: '2-3-4', updated_at_timestamp: 1616164633242313, content_type: ContentType.ItemsKey },
])
queryBuilder.select = jest.fn()
queryBuilder.addSelect = jest.fn()
queryBuilder.where = jest.fn()
queryBuilder.andWhere = jest.fn()
const result = await createRepository().findItemsForComputingIntegrityPayloads('1-2-3')
expect(queryBuilder.select).toHaveBeenCalledWith('item.uuid', 'uuid')
expect(queryBuilder.addSelect).toHaveBeenNthCalledWith(1, 'item.updated_at_timestamp', 'updated_at_timestamp')
expect(queryBuilder.addSelect).toHaveBeenNthCalledWith(2, 'item.content_type', 'content_type')
expect(queryBuilder.where).toHaveBeenCalledTimes(1)
expect(queryBuilder.where).toHaveBeenNthCalledWith(1, 'item.user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(queryBuilder.andWhere).toHaveBeenCalledTimes(1)
expect(queryBuilder.andWhere).toHaveBeenNthCalledWith(1, 'item.deleted = :deleted', { deleted: false })
expect(result.length).toEqual(2)
expect(result[0]).toEqual({
uuid: '2-3-4',
updated_at_timestamp: 1616164633242313,
content_type: ContentType.ItemsKey,
})
expect(result[1]).toEqual({ uuid: '1-2-3', updated_at_timestamp: 1616164633241312, content_type: ContentType.Note })
})
it('should find item by uuid and mark it for deletion', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.update = jest.fn().mockReturnThis()
queryBuilder.update().set = jest.fn().mockReturnThis()
queryBuilder.execute = jest.fn()
const item = { uuid: 'e-1-2-3' } as jest.Mocked<Item>
const updatedAtTimestamp = timer.getTimestampInMicroseconds()
await createRepository().markItemsAsDeleted([item.uuid], updatedAtTimestamp)
expect(queryBuilder.update).toHaveBeenCalled()
expect(queryBuilder.update().set).toHaveBeenCalledWith(
expect.objectContaining({
deleted: true,
content: null,
encItemKey: null,
authHash: null,
updatedAtTimestamp: expect.anything(),
}),
)
expect(queryBuilder.where).toHaveBeenCalledWith('uuid IN (:...uuids)', {
uuids: ['e-1-2-3'],
})
expect(queryBuilder.execute).toHaveBeenCalled()
})
it('should update item content size', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.update = jest.fn().mockReturnThis()
queryBuilder.update().set = jest.fn().mockReturnThis()
queryBuilder.execute = jest.fn()
await createRepository().updateContentSize('1-2-3', 345)
expect(queryBuilder.update).toHaveBeenCalled()
expect(queryBuilder.update().set).toHaveBeenCalledWith(
expect.objectContaining({
contentSize: 345,
}),
)
expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :itemUuid', {
itemUuid: '1-2-3',
})
expect(queryBuilder.execute).toHaveBeenCalled()
})
})

View File

@@ -131,10 +131,12 @@ export class MySQLItemRepository implements ItemRepositoryInterface {
private createFindAllQueryBuilder(query: ItemQuery): SelectQueryBuilder<Item> {
const queryBuilder = this.ormRepository.createQueryBuilder('item')
queryBuilder.orderBy(`item.${query.sortBy}`, query.sortOrder)
if (query.sortBy !== undefined && query.sortOrder !== undefined) {
queryBuilder.orderBy(`item.${query.sortBy}`, query.sortOrder)
}
if (query.selectFields !== undefined) {
queryBuilder.select(query.selectFields.map((field) => `item.${field}`))
queryBuilder.select(query.selectFields)
}
if (query.userUuid !== undefined) {
queryBuilder.where('item.user_uuid = :userUuid', { userUuid: query.userUuid })
@@ -146,15 +148,22 @@ export class MySQLItemRepository implements ItemRepositoryInterface {
queryBuilder.andWhere('item.deleted = :deleted', { deleted: query.deleted })
}
if (query.contentType) {
queryBuilder.andWhere('item.content_type = :contentType', { contentType: query.contentType })
if (Array.isArray(query.contentType)) {
queryBuilder.andWhere('item.content_type IN (:...contentTypes)', { contentTypes: query.contentType })
} else {
queryBuilder.andWhere('item.content_type = :contentType', { contentType: query.contentType })
}
}
if (query.lastSyncTime && query.syncTimeComparison) {
queryBuilder.andWhere(`item.updated_at_timestamp ${query.syncTimeComparison} :lastSyncTime`, {
lastSyncTime: query.lastSyncTime,
})
}
if (query.createdBefore !== undefined) {
queryBuilder.andWhere('item.created_at < :createdAt', { createdAt: query.createdBefore.toISOString() })
if (query.createdBetween !== undefined) {
queryBuilder.andWhere('item.created_at BETWEEN :createdAfter AND :createdBefore', {
createdAfter: query.createdBetween[0].toISOString(),
createdBefore: query.createdBetween[1].toISOString(),
})
}
if (query.offset !== undefined) {
queryBuilder.skip(query.offset)