wip: default rotation

This commit is contained in:
Mo
2023-05-19 12:59:28 -05:00
parent 417b0257cf
commit c416ed6078
40 changed files with 571 additions and 131 deletions
@@ -1,3 +1,8 @@
import {
DomainEventHandlerInterface,
DomainEventMessageHandlerInterface,
DomainEventSubscriberFactoryInterface,
} from '@standardnotes/domain-events'
import { GroupServiceInterface } from './../Domain/Group/Service/GroupServiceInterface'
import { GroupUserKeyFactory } from './../Domain/GroupUserKey/GroupUserKeyFactory'
import { GroupFactoryInterface } from '../Domain/Group/Factory/GroupFactoryInterface'
@@ -45,6 +50,12 @@ import { GroupFactory } from '../Domain/Group/Factory/GroupFactory'
import { GroupService } from '../Domain/Group/Service/GroupService'
import { GroupUserKeyService } from '../Domain/GroupUserKey/Service/GroupUserKeyService'
import { GroupUserKeyServiceInterface } from '../Domain/GroupUserKey/Service/GroupUserKeyServiceInterface'
import {
SQSDomainEventSubscriberFactory,
SQSEventMessageHandler,
SQSNewRelicEventMessageHandler,
} from '@standardnotes/domain-events-infra'
import { UserCredentialsChangedEventHandler } from '../Domain/Handler/UserCredentialsChangedEventHandler'
export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -206,6 +217,42 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
return new GroupUserKeyFactory(context.container.get(TYPES.Timer))
})
container
.bind<UserCredentialsChangedEventHandler>(TYPES.UserCredentialsChangedEventHandler)
.toDynamicValue((context: interfaces.Context) => {
return new UserCredentialsChangedEventHandler(
context.container.get(TYPES.ContactRepository),
context.container.get(TYPES.Timer),
)
})
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['USER_CREDENTIALS_CHANGED', container.get(TYPES.UserCredentialsChangedEventHandler)],
])
container
.bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
.toDynamicValue((context: interfaces.Context) => {
const env: Env = context.container.get(TYPES.Env)
const handler =
env.get('NEW_RELIC_ENABLED', true) === 'true'
? new SQSNewRelicEventMessageHandler(eventHandlers, context.container.get(TYPES.Logger))
: new SQSEventMessageHandler(eventHandlers, context.container.get(TYPES.Logger))
return handler
})
container
.bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
.toDynamicValue((context: interfaces.Context) => {
return new SQSDomainEventSubscriberFactory(
context.container.get(TYPES.SQS),
context.container.get(TYPES.SQS_QUEUE_URL),
context.container.get(TYPES.DomainEventMessageHandler),
)
})
container
.bind<OwnershipFilter>(TYPES.OwnershipFilter)
.toDynamicValue(
+21 -6
View File
@@ -6,24 +6,39 @@ const TYPES = {
SQS: Symbol.for('SQS'),
S3: Symbol.for('S3'),
Env: Symbol.for('Env'),
// Repositories
ItemRepository: Symbol.for('ItemRepository'),
ItemLinkRepository: Symbol.for('ItemLinkRepository'),
GroupRepository: Symbol.for('GroupRepository'),
GroupUserKeyRepository: Symbol.for('GroupUserKeyRepository'),
ContactRepository: Symbol.for('ContactRepository'),
// ORM
ORMItemRepository: Symbol.for('ORMItemRepository'),
ORMItemLinkRepository: Symbol.for('ORMItemLinkRepository'),
ORMGroupRepository: Symbol.for('ORMGroupRepository'),
ORMGroupUserKeyRepository: Symbol.for('ORMGroupUserKeyRepository'),
ORMContactRepository: Symbol.for('ORMContactRepository'),
// Middleware
AuthMiddleware: Symbol.for('AuthMiddleware'),
// Projectors
ItemProjector: Symbol.for('ItemProjector'),
SavedItemProjector: Symbol.for('SavedItemProjector'),
ItemConflictProjector: Symbol.for('ItemConflictProjector'),
GroupProjector: Symbol.for('GroupProjector'),
GroupUserKeyProjector: Symbol.for('GroupUserKeyProjector'),
ContactProjector: Symbol.for('ContactProjector'),
// factories
ItemFactory: Symbol.for('ItemFactory'),
ItemLinkFactory: Symbol.for('ItemLinkFactory'),
GroupFactory: Symbol.for('GroupFactory'),
GroupUserKeyFactory: Symbol.for('GroupUserKeyFactory'),
ContactFactory: Symbol.for('ContactFactory'),
// env vars
REDIS_URL: Symbol.for('REDIS_URL'),
SNS_TOPIC_ARN: Symbol.for('SNS_TOPIC_ARN'),
@@ -53,11 +68,17 @@ const TYPES = {
GetUserItemLinks: Symbol.for('GetUserItemLinks'),
CreateSharedFileValetToken: Symbol.for('CreateSharedFileValetToken'),
// Handlers
UserCredentialsChangedEventHandler: Symbol.for('UserCredentialsChangedEventHandler'),
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
DuplicateItemSyncedEventHandler: Symbol.for('DuplicateItemSyncedEventHandler'),
EmailBackupRequestedEventHandler: Symbol.for('EmailBackupRequestedEventHandler'),
ItemRevisionCreationRequestedEventHandler: Symbol.for('ItemRevisionCreationRequestedEventHandler'),
// Services
GroupService: Symbol.for('GroupService'),
GroupUserKeyService: Symbol.for('GroupUserKeyService'),
ContactService: Symbol.for('ContactService'),
ContentDecoder: Symbol.for('ContentDecoder'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
@@ -79,14 +100,8 @@ const TYPES = {
UuidFilter: Symbol.for('UuidFilter'),
ContentTypeFilter: Symbol.for('ContentTypeFilter'),
ContentFilter: Symbol.for('ContentFilter'),
ItemFactory: Symbol.for('ItemFactory'),
ItemLinkFactory: Symbol.for('ItemLinkFactory'),
GroupFactory: Symbol.for('GroupFactory'),
GroupUserKeyFactory: Symbol.for('GroupUserKeyFactory'),
ItemTransferCalculator: Symbol.for('ItemTransferCalculator'),
ValetTokenEncoder: Symbol.for('ValetTokenEncoder'),
GroupService: Symbol.for('GroupService'),
GroupUserKeyService: Symbol.for('GroupUserKeyService'),
}
export default TYPES
@@ -0,0 +1,64 @@
import { ContactServiceInterface } from './../Domain/Contact/Service/ContactServiceInterface'
import { Request, Response } from 'express'
import { BaseHttpController, controller, httpPost, results, httpDelete } from 'inversify-express-utils'
import TYPES from '../Bootstrap/Types'
import { inject } from 'inversify'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { Contact } from '../Domain/Contact/Model/Contact'
import { ContactProjection } from '../Projection/ContactProjection'
@controller('/contacts')
export class ContactsController extends BaseHttpController {
constructor(
@inject(TYPES.ContactService) private contactService: ContactServiceInterface,
@inject(TYPES.ContactProjector) private contactProjector: ProjectorInterface<Contact, ContactProjection>,
) {
super()
}
@httpPost('/', TYPES.AuthMiddleware)
public async createContact(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.contactService.createContact({
userUuid: response.locals.user.uuid,
contactUuid: request.body.contact_uuid,
contactPublicKey: request.body.contact_public_key,
})
if (!result) {
return this.errorResponse(500, 'Could not create contact')
}
return this.json({
contact: this.contactProjector.projectFull(result),
})
}
@httpDelete('/:contactUuid', TYPES.AuthMiddleware)
public async deleteContact(
request: Request,
response: Response,
): Promise<results.NotFoundResult | results.JsonResult> {
const result = await this.contactService.deleteContact({
originatorUuid: response.locals.user.uuid,
contactUuid: request.params.contactUuid,
})
if (!result) {
return this.errorResponse(500, 'Could not delete contact')
}
return this.json({ success: true })
}
private errorResponse(status: number, message?: string, tag?: string) {
return this.json(
{
error: { message, tag },
},
status,
)
}
}
@@ -86,7 +86,7 @@ export class GroupsController extends BaseHttpController {
return this.json({ success: true })
}
@httpPatch('/all-user-keys', TYPES.AuthMiddleware)
@httpPatch('/user-keys', TYPES.AuthMiddleware)
public async updateAllUserKeysOfUser(
request: Request,
response: Response,
@@ -0,0 +1,31 @@
import { TimerInterface } from '@standardnotes/time'
import { ContactFactoryInterface } from './ContactFactoryInterface'
import { ContactHash } from './ContactHash'
import { Contact } from '../Model/Contact'
export class ContactFactory implements ContactFactoryInterface {
constructor(private timer: TimerInterface) {}
create(dto: { userUuid: string; contactHash: ContactHash }): Contact {
const newContact = new Contact()
newContact.uuid = dto.contactHash.uuid
newContact.userUuid = dto.userUuid
newContact.contactUuid = dto.contactHash.contact_uuid
newContact.contactPublicKey = dto.contactHash.contact_public_key
const now = this.timer.getTimestampInSeconds()
newContact.updatedAtTimestamp = now
newContact.createdAtTimestamp = now
if (dto.contactHash.created_at_timestamp) {
newContact.createdAtTimestamp = dto.contactHash.created_at_timestamp
}
return newContact
}
createStub(dto: { userUuid: string; contactHash: ContactHash }): Contact {
const item = this.create(dto)
return item
}
}
@@ -0,0 +1,7 @@
import { Contact } from '../Model/Contact'
import { ContactHash } from './ContactHash'
export interface ContactFactoryInterface {
create(dto: { userUuid: string; contactHash: ContactHash }): Contact
createStub(dto: { userUuid: string; contactHash: ContactHash }): Contact
}
@@ -0,0 +1,8 @@
export type ContactHash = {
uuid: string
user_uuid: string
contact_uuid: string
contact_public_key: string
created_at_timestamp?: number
updated_at_timestamp?: number
}
@@ -0,0 +1,36 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'contacts' })
export class Contact {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'user_uuid',
length: 36,
})
declare userUuid: string
@Column({
name: 'contact_uuid',
length: 36,
})
declare contactUuid: string
@Column({
name: 'contact_public_key',
})
declare contactPublicKey: string
@Column({
name: 'created_at_timestamp',
type: 'bigint',
})
declare createdAtTimestamp: number
@Column({
name: 'updated_at_timestamp',
type: 'bigint',
})
declare updatedAtTimestamp: number
}
@@ -0,0 +1,15 @@
import { Contact } from '../Model/Contact'
export type ContactQuery = {
userUuid?: string
contactUuid?: string
lastSyncTime?: number
}
export interface ContactsRepositoryInterface {
findByUuid(uuid: string): Promise<Contact | null>
create(contact: Contact): Promise<Contact>
save(contact: Contact): Promise<Contact>
remove(contact: Contact): Promise<Contact>
findAll(query: ContactQuery): Promise<Contact[]>
}
@@ -0,0 +1,52 @@
import { Repository, SelectQueryBuilder } from 'typeorm'
import { ContactQuery, ContactsRepositoryInterface } from './ContactRepositoryInterface'
import { Contact } from '../Model/Contact'
export class TypeORMContactRepository implements ContactsRepositoryInterface {
constructor(private ormRepository: Repository<Contact>) {}
async create(contact: Contact): Promise<Contact> {
return this.ormRepository.save(contact)
}
async save(contact: Contact): Promise<Contact> {
return this.ormRepository.save(contact)
}
findByUuid(uuid: string): Promise<Contact | null> {
return this.ormRepository
.createQueryBuilder('contact')
.where('contact.uuid = :uuid', {
uuid,
})
.getOne()
}
async remove(contact: Contact): Promise<Contact> {
return this.ormRepository.remove(contact)
}
async findAll(query: ContactQuery): Promise<Contact[]> {
return this.createFindAllQueryBuilder(query).getMany()
}
private createFindAllQueryBuilder(query: ContactQuery): SelectQueryBuilder<Contact> {
const queryBuilder = this.ormRepository.createQueryBuilder('contact')
if (query.userUuid) {
queryBuilder.where('contact.user_uuid = :userUuid', { userUuid: query.userUuid })
}
if (query.contactUuid) {
queryBuilder.andWhere('contact.contact_uuid = :contactUuid', { contactUuid: query.contactUuid })
}
if (query.lastSyncTime) {
queryBuilder.andWhere('group_user_key.updated_at_timestamp > :lastSyncTime', {
lastSyncTime: query.lastSyncTime,
})
}
return queryBuilder
}
}
@@ -0,0 +1,52 @@
import { v4 as uuidv4 } from 'uuid'
import { TimerInterface } from '@standardnotes/time'
import { Contact } from '../Model/Contact'
import { ContactsRepositoryInterface } from '../Repository/ContactRepositoryInterface'
import { ContactServiceInterface } from './ContactServiceInterface'
import { ContactFactoryInterface } from '../Factory/ContactFactoryInterface'
export class ContactService implements ContactServiceInterface {
constructor(
private contactRepository: ContactsRepositoryInterface,
private contactFactory: ContactFactoryInterface,
private timer: TimerInterface,
) {}
async createContact(dto: {
userUuid: string
contactUuid: string
contactPublicKey: string
}): Promise<Contact | null> {
const contact = this.contactFactory.create({
userUuid: dto.userUuid,
contactHash: {
uuid: uuidv4(),
user_uuid: dto.userUuid,
contact_uuid: dto.contactUuid,
contact_public_key: dto.contactPublicKey,
created_at_timestamp: this.timer.getTimestampInSeconds(),
updated_at_timestamp: this.timer.getTimestampInSeconds(),
},
})
const savedContact = await this.contactRepository.create(contact)
return savedContact
}
async deleteContact(dto: { contactUuid: string; originatorUuid: string }): Promise<boolean> {
const contact = await this.contactRepository.findByUuid(dto.contactUuid)
if (!contact || contact.userUuid !== dto.originatorUuid) {
return false
}
await this.contactRepository.remove(contact)
return true
}
async getUserContacts(dto: { userUuid: string; lastSyncTime?: number }): Promise<Contact[]> {
return this.contactRepository.findAll({ userUuid: dto.userUuid, lastSyncTime: dto.lastSyncTime })
}
}
@@ -0,0 +1,9 @@
import { Contact } from '../Model/Contact'
export interface ContactServiceInterface {
createContact(dto: { userUuid: string; contactUuid: string; contactPublicKey: string }): Promise<Contact | null>
deleteContact(dto: { contactUuid: string; originatorUuid: string }): Promise<boolean>
getUserContacts(dto: { userUuid: string; lastSyncTime?: number }): Promise<Contact[]>
}
@@ -8,6 +8,7 @@ export type GroupUserKeyFindAllForUserQuery = {
userUuid: string
lastSyncTime?: number
senderUuid?: string
includeSentAndReceived?: boolean
}
export type GroupUserKeyFindAllForGroup = {
@@ -16,7 +17,7 @@ export type GroupUserKeyFindAllForGroup = {
export interface GroupUserKeyRepositoryInterface {
findByUuid(groupUserUuid: string): Promise<GroupUserKey | null>
findByUserAndUuid(userUuid: string, userKeyUuid: string): Promise<GroupUserKey | null>
findAsSenderOrRecipientByUuid(userUuid: string, userKeyUuid: string): Promise<GroupUserKey | null>
create(group: GroupUserKey): Promise<GroupUserKey>
save(groupUser: GroupUserKey): Promise<GroupUserKey>
remove(group: GroupUserKey): Promise<GroupUserKey>
@@ -1,5 +1,5 @@
import { GroupUserKey } from '../Model/GroupUserKey'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { Brackets, Repository, SelectQueryBuilder } from 'typeorm'
import {
GroupUserKeyFindAllForGroup,
GroupUserKeyFindAllForUserQuery,
@@ -26,10 +26,15 @@ export class TypeORMGroupUserKeyRepository implements GroupUserKeyRepositoryInte
.getOne()
}
findByUserAndUuid(userUuid: string, userKeyUuid: string): Promise<GroupUserKey | null> {
findAsSenderOrRecipientByUuid(userUuid: string, userKeyUuid: string): Promise<GroupUserKey | null> {
return this.ormRepository
.createQueryBuilder('group_user_key')
.where('group_user_key.user_uuid = :userUuid', { userUuid })
.where(
new Brackets((qb) => {
qb.where('group_user_key.user_uuid = :userUuid', { userUuid })
qb.orWhere('group_user_key.sender_uuid = :userUuid', { userUuid })
}),
)
.andWhere('group_user_key.uuid = :userKeyUuid', { userKeyUuid })
.getOne()
}
@@ -62,6 +67,10 @@ export class TypeORMGroupUserKeyRepository implements GroupUserKeyRepositoryInte
queryBuilder.where('group_user_key.user_uuid = :userUuid', { userUuid: query.userUuid })
if (query.includeSentAndReceived) {
queryBuilder.orWhere('group_user_key.sender_uuid = :userUuid', { userUuid: query.userUuid })
}
if (query.lastSyncTime) {
queryBuilder.andWhere('group_user_key.updated_at_timestamp > :lastSyncTime', {
lastSyncTime: query.lastSyncTime,
@@ -87,7 +87,7 @@ export class GroupUserKeyService implements GroupUserKeyServiceInterface {
}[]
}): Promise<boolean> {
for (const updatedKey of dto.updatedKeys) {
const userKey = await this.groupUserRepository.findByUserAndUuid(dto.userUuid, updatedKey.uuid)
const userKey = await this.groupUserRepository.findAsSenderOrRecipientByUuid(dto.userUuid, updatedKey.uuid)
if (!userKey) {
continue
}
@@ -170,6 +170,7 @@ export class GroupUserKeyService implements GroupUserKeyServiceInterface {
async getAllUserKeysForUser(dto: { userUuid: string }): Promise<GroupUserKey[]> {
return this.groupUserRepository.findAllForUser({
userUuid: dto.userUuid,
includeSentAndReceived: true,
})
}
@@ -0,0 +1,17 @@
import { DomainEventHandlerInterface, UserCredentialsChangedEvent } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { ContactsRepositoryInterface } from '../Contact/Repository/ContactRepositoryInterface'
export class UserCredentialsChangedEventHandler implements DomainEventHandlerInterface {
constructor(private contactRepository: ContactsRepositoryInterface, private timer: TimerInterface) {}
async handle(event: UserCredentialsChangedEvent): Promise<void> {
const contacts = await this.contactRepository.findAll({ contactUuid: event.payload.userUuid })
for (const contact of contacts) {
contact.contactPublicKey = event.payload.newPublicKey
contact.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
await this.contactRepository.save(contact)
}
}
}
@@ -1,3 +1,4 @@
import { ContactProjection } from '../../../Projection/ContactProjection'
import { GroupUserKeyProjection } from '../../../Projection/GroupUserKeyProjection'
import { ItemConflictProjection } from '../../../Projection/ItemConflictProjection'
import { ItemProjection } from '../../../Projection/ItemProjection'
@@ -10,4 +11,5 @@ export type SyncResponse20200115 = {
sync_token: string
cursor_token?: string
group_keys: Array<GroupUserKeyProjection>
contacts: Array<ContactProjection>
}
@@ -1,3 +1,4 @@
import { Contact } from './../../Contact/Model/Contact'
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
import { SyncItemsResponse } from '../../UseCase/SyncItemsResponse'
import { Item } from '../Item'
@@ -9,6 +10,7 @@ import { SyncResponseFactoryInterface } from './SyncResponseFactoryInterface'
import { SavedItemProjection } from '../../../Projection/SavedItemProjection'
import { GroupUserKey } from '../../GroupUserKey/Model/GroupUserKey'
import { GroupUserKeyProjection } from '../../../Projection/GroupUserKeyProjection'
import { ContactProjection } from '../../../Projection/ContactProjection'
export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface {
constructor(
@@ -16,27 +18,33 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
private itemConflictProjector: ProjectorInterface<ItemConflict, ItemConflictProjection>,
private savedItemProjector: ProjectorInterface<Item, SavedItemProjection>,
private groupUserKeyProjector: ProjectorInterface<GroupUserKey, GroupUserKeyProjection>,
private contactProjector: ProjectorInterface<Contact, ContactProjection>,
) {}
async createResponse(syncItemsResponse: SyncItemsResponse): Promise<SyncResponse20200115> {
const retrievedItems = []
for (const item of syncItemsResponse.retrievedItems) {
retrievedItems.push(<ItemProjection>await this.itemProjector.projectFull(item))
retrievedItems.push(<ItemProjection>this.itemProjector.projectFull(item))
}
const savedItems = []
for (const item of syncItemsResponse.savedItems) {
savedItems.push(<SavedItemProjection>await this.savedItemProjector.projectFull(item))
savedItems.push(<SavedItemProjection>this.savedItemProjector.projectFull(item))
}
const conflicts = []
for (const itemConflict of syncItemsResponse.conflicts) {
conflicts.push(<ItemConflictProjection>await this.itemConflictProjector.projectFull(itemConflict))
conflicts.push(<ItemConflictProjection>this.itemConflictProjector.projectFull(itemConflict))
}
const groupKeys = []
for (const groupKey of syncItemsResponse.groupKeys) {
groupKeys.push(<GroupUserKeyProjection>await this.groupUserKeyProjector.projectFull(groupKey))
groupKeys.push(<GroupUserKeyProjection>this.groupUserKeyProjector.projectFull(groupKey))
}
const contacts = []
for (const contact of syncItemsResponse.contacts) {
contacts.push(<ContactProjection>this.contactProjector.projectFull(contact))
}
return {
@@ -46,6 +54,7 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
sync_token: syncItemsResponse.syncToken,
cursor_token: syncItemsResponse.cursorToken,
group_keys: groupKeys,
contacts,
}
}
}
@@ -1,3 +1,4 @@
import { ContactServiceInterface } from './../Contact/Service/ContactServiceInterface'
import { GroupUserKey } from '../GroupUserKey/Model/GroupUserKey'
import { GroupUserKeyServiceInterface } from '../GroupUserKey/Service/GroupUserKeyServiceInterface'
import { Item } from '../Item/Item'
@@ -8,7 +9,11 @@ import { SyncItemsResponse } from './SyncItemsResponse'
import { UseCaseInterface } from './UseCaseInterface'
export class SyncItems implements UseCaseInterface {
constructor(private itemService: ItemServiceInterface, private groupUserService: GroupUserKeyServiceInterface) {}
constructor(
private itemService: ItemServiceInterface,
private groupUserService: GroupUserKeyServiceInterface,
private contactService: ContactServiceInterface,
) {}
async execute(dto: SyncItemsDTO): Promise<SyncItemsResponse> {
const getItemsResult = await this.itemService.getItems({
@@ -34,20 +39,25 @@ export class SyncItems implements UseCaseInterface {
retrievedItems = await this.itemService.frontLoadKeysItemsToTop(dto.userUuid, retrievedItems)
}
const lastSyncTime = this.itemService.getLastSyncTime({
syncToken: dto.syncToken,
cursorToken: dto.cursorToken,
})
let newUserKeys: GroupUserKey[] = []
const isNotPerformingGroupSpecificSync = dto.groupUuids == undefined || dto.groupUuids.length === 0
if (isNotPerformingGroupSpecificSync) {
const lastSyncTime = this.itemService.getLastSyncTime({
syncToken: dto.syncToken,
cursorToken: dto.cursorToken,
})
newUserKeys = await this.groupUserService.getGroupUserKeysForUser({
userUuid: dto.userUuid,
lastSyncTime,
})
}
const contacts = await this.contactService.getUserContacts({
userUuid: dto.userUuid,
lastSyncTime: lastSyncTime,
})
const syncResponse: SyncItemsResponse = {
retrievedItems,
syncToken: saveItemsResult.syncToken,
@@ -55,6 +65,7 @@ export class SyncItems implements UseCaseInterface {
conflicts: saveItemsResult.conflicts,
cursorToken: getItemsResult.cursorToken,
groupKeys: newUserKeys,
contacts,
}
return syncResponse
@@ -1,3 +1,4 @@
import { Contact } from '../Contact/Model/Contact'
import { GroupUserKey } from '../GroupUserKey/Model/GroupUserKey'
import { Item } from '../Item/Item'
import { ItemConflict } from '../Item/ItemConflict'
@@ -7,6 +8,7 @@ export type SyncItemsResponse = {
savedItems: Array<Item>
conflicts: Array<ItemConflict>
groupKeys: Array<GroupUserKey>
contacts: Array<Contact>
syncToken: string
cursorToken?: string
}
@@ -0,0 +1,8 @@
export type ContactProjection = {
uuid: string
user_uuid: string
contact_uuid: string
contact_public_key: string
created_at_timestamp: number
updated_at_timestamp: number
}
@@ -0,0 +1,29 @@
import { Contact } from '../Domain/Contact/Model/Contact'
import { ProjectorInterface } from './ProjectorInterface'
import { ContactProjection } from './ContactProjection'
export class ContactProjector implements ProjectorInterface<Contact, ContactProjection> {
projectSimple(_userKey: Contact): ContactProjection {
throw Error('not implemented')
}
projectCustom(_projectionType: string, userKey: Contact): ContactProjection {
const fullProjection = this.projectFull(userKey)
return {
...fullProjection,
user_uuid: userKey.userUuid,
}
}
projectFull(userKey: Contact): ContactProjection {
return {
uuid: userKey.uuid,
user_uuid: userKey.userUuid,
contact_uuid: userKey.contactUuid,
contact_public_key: userKey.contactPublicKey,
created_at_timestamp: userKey.createdAtTimestamp,
updated_at_timestamp: userKey.updatedAtTimestamp,
}
}
}