Compare commits

..

5 Commits

Author SHA1 Message Date
standardci
8b3d78678f chore(release): publish new version
- @standardnotes/analytics@2.11.16
 - @standardnotes/domain-core@1.5.2
 - @standardnotes/revisions-server@1.9.1
 - @standardnotes/syncing-server@1.20.1
2022-12-02 08:30:40 +00:00
Karol Sójko
2351cd3ad6 fix(revisions): change timestamps to dates value object 2022-12-01 11:31:11 +01:00
Karol Sójko
dd86c5bcdf fix(domain-core): rename timestamps to dates 2022-12-01 11:25:38 +01:00
standardci
d0c00e306e chore(release): publish new version
- @standardnotes/syncing-server@1.20.0
2022-11-30 17:15:44 +00:00
Karol Sójko
6cd68ddd6a feat(syncing-server): add revisions ownership fix procedure 2022-11-30 18:13:43 +01:00
25 changed files with 205 additions and 66 deletions

View File

@@ -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.11.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.15...@standardnotes/analytics@2.11.16) (2022-12-02)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.14...@standardnotes/analytics@2.11.15) (2022-11-30)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.11.15",
"version": "2.11.16",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -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.5.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.5.1...@standardnotes/domain-core@1.5.2) (2022-12-02)
### Bug Fixes
* **domain-core:** rename timestamps to dates ([dd86c5b](https://github.com/standardnotes/server/commit/dd86c5bcdf3a1a37d684f6416d4cc6f24497fe5e))
## [1.5.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.5.0...@standardnotes/domain-core@1.5.1) (2022-11-25)
**Note:** Version bump only for package @standardnotes/domain-core

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-core",
"version": "1.5.1",
"version": "1.5.2",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -1,8 +1,8 @@
import { Timestamps } from './Timestamps'
import { Dates } from './Dates'
describe('Timestamps', () => {
describe('Dates', () => {
it('should create a value object', () => {
const valueOrError = Timestamps.create(new Date(1), new Date(2))
const valueOrError = Dates.create(new Date(1), new Date(2))
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().createdAt).toEqual(new Date(1))
@@ -10,11 +10,11 @@ describe('Timestamps', () => {
})
it('should not create an invalid value object', () => {
let valueOrError = Timestamps.create(null as unknown as Date, '2' as unknown as Date)
let valueOrError = Dates.create(null as unknown as Date, '2' as unknown as Date)
expect(valueOrError.isFailed()).toBeTruthy()
valueOrError = Timestamps.create(new Date(2), '2' as unknown as Date)
valueOrError = Dates.create(new Date(2), '2' as unknown as Date)
expect(valueOrError.isFailed()).toBeTruthy()
})

View File

@@ -0,0 +1,28 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { DatesProps } from './DatesProps'
export class Dates extends ValueObject<DatesProps> {
get createdAt(): Date {
return this.props.createdAt
}
get updatedAt(): Date {
return this.props.updatedAt
}
private constructor(props: DatesProps) {
super(props)
}
static create(createdAt: Date, updatedAt: Date): Result<Dates> {
if (!(createdAt instanceof Date)) {
return Result.fail<Dates>(`Could not create Dates. Creation date should be a date object, given: ${createdAt}`)
}
if (!(updatedAt instanceof Date)) {
return Result.fail<Dates>(`Could not create Dates. Update date should be a date object, given: ${createdAt}`)
}
return Result.ok<Dates>(new Dates({ createdAt, updatedAt }))
}
}

View File

@@ -1,4 +1,4 @@
export interface TimestampsProps {
export interface DatesProps {
createdAt: Date
updatedAt: Date
}

View File

@@ -1,32 +0,0 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { TimestampsProps } from './TimestampsProps'
export class Timestamps extends ValueObject<TimestampsProps> {
get createdAt(): Date {
return this.props.createdAt
}
get updatedAt(): Date {
return this.props.updatedAt
}
private constructor(props: TimestampsProps) {
super(props)
}
static create(createdAt: Date, updatedAt: Date): Result<Timestamps> {
if (!(createdAt instanceof Date)) {
return Result.fail<Timestamps>(
`Could not create Timestamps. Creation date should be a date object, given: ${createdAt}`,
)
}
if (!(updatedAt instanceof Date)) {
return Result.fail<Timestamps>(
`Could not create Timestamps. Update date should be a date object, given: ${createdAt}`,
)
}
return Result.ok<Timestamps>(new Timestamps({ createdAt, updatedAt }))
}
}

View File

@@ -1,11 +1,11 @@
export * from './Common/Dates'
export * from './Common/DatesProps'
export * from './Common/Email'
export * from './Common/EmailProps'
export * from './Common/RoleName'
export * from './Common/RoleNameProps'
export * from './Common/RoleNameCollection'
export * from './Common/RoleNameCollectionProps'
export * from './Common/Timestamps'
export * from './Common/TimestampsProps'
export * from './Common/Uuid'
export * from './Common/UuidProps'

View File

@@ -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.9.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.0...@standardnotes/revisions-server@1.9.1) (2022-12-02)
### Bug Fixes
* **revisions:** change timestamps to dates value object ([2351cd3](https://github.com/standardnotes/server/commit/2351cd3ad660c81b3b6bbc3759bc1c32a03406af))
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.8.2...@standardnotes/revisions-server@1.9.0) (2022-11-30)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.9.0",
"version": "1.9.1",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -1,4 +1,4 @@
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { Dates, Uuid } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
import { Revision } from './Revision'
@@ -13,7 +13,7 @@ describe('Revision', () => {
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
timestamps: Timestamps.create(new Date(1), new Date(2)).getValue(),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()

View File

@@ -1,8 +1,8 @@
import { Timestamps } from '@standardnotes/domain-core'
import { Dates } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
export interface RevisionMetadataProps {
contentType: ContentType
timestamps: Timestamps
dates: Dates
}

View File

@@ -1,4 +1,4 @@
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { Dates, Uuid } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
@@ -11,5 +11,5 @@ export interface RevisionProps {
encItemKey: string | null
authHash: string | null
creationDate: Date
timestamps: Timestamps
dates: Dates
}

View File

@@ -1,4 +1,4 @@
import { MapperInterface, Timestamps, Uuid } from '@standardnotes/domain-core'
import { MapperInterface, Dates, Uuid } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { Revision } from '../Domain/Revision/Revision'
@@ -34,7 +34,7 @@ export class RevisionItemStringMapper implements MapperInterface<Revision, strin
itemsKeyId: item.items_key_id,
encItemKey: item.enc_item_key,
creationDate: new Date(),
timestamps: Timestamps.create(new Date(), new Date()).getValue(),
dates: Dates.create(new Date(), new Date()).getValue(),
})
if (revisionOrError.isFailed()) {

View File

@@ -1,4 +1,4 @@
import { MapperInterface, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
import { MapperInterface, Dates, UniqueEntityId } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
@@ -12,16 +12,16 @@ export class RevisionMetadataPersistenceMapper implements MapperInterface<Revisi
}
const contentType = contentTypeOrError.getValue()
const timestampsOrError = Timestamps.create(projection.createdAt, projection.updatedAt)
if (timestampsOrError.isFailed()) {
throw new Error(`Could not create timestamps: ${timestampsOrError.getError()}`)
const datesOrError = Dates.create(projection.createdAt, projection.updatedAt)
if (datesOrError.isFailed()) {
throw new Error(`Could not create dates: ${datesOrError.getError()}`)
}
const timestamps = timestampsOrError.getValue()
const dates = datesOrError.getValue()
const revisionMetadataOrError = RevisionMetadata.create(
{
contentType,
timestamps,
dates,
},
new UniqueEntityId(projection.uuid),
)

View File

@@ -1,4 +1,4 @@
import { MapperInterface, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { MapperInterface, Dates, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { Revision } from '../Domain/Revision/Revision'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
@@ -11,11 +11,11 @@ export class RevisionPersistenceMapper implements MapperInterface<Revision, Type
}
const contentType = contentTypeOrError.getValue()
const timestampsOrError = Timestamps.create(projection.createdAt, projection.updatedAt)
if (timestampsOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${timestampsOrError.getError()}`)
const datesOrError = Dates.create(projection.createdAt, projection.updatedAt)
if (datesOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${datesOrError.getError()}`)
}
const timestamps = timestampsOrError.getValue()
const dates = datesOrError.getValue()
const itemUuidOrError = Uuid.create(projection.itemUuid)
if (itemUuidOrError.isFailed()) {
@@ -42,7 +42,7 @@ export class RevisionPersistenceMapper implements MapperInterface<Revision, Type
itemsKeyId: projection.itemsKeyId,
itemUuid,
userUuid,
timestamps,
dates,
},
new UniqueEntityId(projection.uuid),
)
@@ -59,8 +59,8 @@ export class RevisionPersistenceMapper implements MapperInterface<Revision, Type
typeormRevision.authHash = domain.props.authHash
typeormRevision.content = domain.props.content
typeormRevision.contentType = domain.props.contentType.value
typeormRevision.createdAt = domain.props.timestamps.createdAt
typeormRevision.updatedAt = domain.props.timestamps.updatedAt
typeormRevision.createdAt = domain.props.dates.createdAt
typeormRevision.updatedAt = domain.props.dates.updatedAt
typeormRevision.creationDate = domain.props.creationDate
typeormRevision.encItemKey = domain.props.encItemKey
typeormRevision.itemUuid = domain.props.itemUuid.value

View File

@@ -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.20.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.0...@standardnotes/syncing-server@1.20.1) (2022-12-02)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.20.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.19.1...@standardnotes/syncing-server@1.20.0) (2022-11-30)
### Features
* **syncing-server:** add revisions ownership fix procedure ([6cd68dd](https://github.com/standardnotes/syncing-server-js/commit/6cd68ddd6af0b1adde6c0d1cb3acef6e1aa9811b))
## [1.19.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.19.0...@standardnotes/syncing-server@1.19.1) (2022-11-30)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

@@ -0,0 +1,78 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
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'
const fixRevisionsOwnership = async (
itemRepository: ItemRepositoryInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
logger: Logger,
): Promise<void> => {
const stream = await itemRepository.streamAll({
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
createdBefore: new Date('2022-11-23'),
selectFields: ['user_uuid', 'item_uuid'],
})
return new Promise((resolve, reject) => {
stream
.pipe(
new Stream.Transform({
objectMode: true,
transform: async (rawItemData, _encoding, callback) => {
try {
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}`)
}
callback()
},
}),
)
.on('finish', resolve)
.on('error', reject)
})
}
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting revisions ownership fixing')
const itemRepository: ItemRepositoryInterface = container.get(TYPES.ItemRepository)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
Promise.resolve(fixRevisionsOwnership(itemRepository, domainEventFactory, domainEventPublisher, logger))
.then(() => {
logger.info('revisions ownership fix complete.')
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish revisions ownership fix: ${error.message}`)
process.exit(1)
})
})

View File

@@ -25,6 +25,11 @@ case "$COMMAND" in
yarn workspace @standardnotes/syncing-server content-size $USER_UUID
;;
'revisions-ownership-fix' )
echo "Starting Revisions Ownership Fixing..."
yarn workspace @standardnotes/syncing-server revisions-ownership
;;
* )
echo "Unknown command"
;;

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.19.1",
"version": "1.20.1",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -22,6 +22,7 @@
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js",
"content-size": "yarn node dist/bin/content.js",
"revisions-ownership": "yarn node dist/bin/revisions.js",
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
},
"dependencies": {

View File

@@ -11,6 +11,7 @@ import {
ItemsSyncedEvent,
OneDriveBackupFailedEvent,
RevisionsCopyRequestedEvent,
RevisionsOwnershipUpdateRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
@@ -22,6 +23,24 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createRevisionsOwnershipUpdateRequestedEvent(dto: {
userUuid: string
itemUuid: string
}): RevisionsOwnershipUpdateRequestedEvent {
return {
type: 'REVISIONS_OWNERSHIP_UPDATE_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
},
payload: dto,
}
}
createRevisionsCopyRequestedEvent(
userUuid: string,
dto: {

View File

@@ -9,6 +9,7 @@ import {
ItemsSyncedEvent,
OneDriveBackupFailedEvent,
RevisionsCopyRequestedEvent,
RevisionsOwnershipUpdateRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
@@ -40,4 +41,8 @@ export interface DomainEventFactoryInterface {
userUuid: string,
dto: { originalItemUuid: string; newItemUuid: string },
): RevisionsCopyRequestedEvent
createRevisionsOwnershipUpdateRequestedEvent(dto: {
userUuid: string
itemUuid: string
}): RevisionsOwnershipUpdateRequestedEvent
}

View File

@@ -9,4 +9,6 @@ export type ItemQuery = {
deleted?: boolean
offset?: number
limit?: number
createdBefore?: Date
selectFields?: string[]
}

View File

@@ -130,8 +130,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.selectFields !== undefined) {
queryBuilder.select(query.selectFields.map((field) => `item.${field}`))
}
if (query.userUuid !== undefined) {
queryBuilder.where('item.user_uuid = :userUuid', { userUuid: query.userUuid })
}
@@ -149,6 +153,9 @@ export class MySQLItemRepository implements ItemRepositoryInterface {
lastSyncTime: query.lastSyncTime,
})
}
if (query.createdBefore !== undefined) {
queryBuilder.andWhere('item.created_at < :createdAt', { createdAt: query.createdBefore.toISOString() })
}
if (query.offset !== undefined) {
queryBuilder.skip(query.offset)
}