mirror of
https://github.com/standardnotes/server
synced 2026-10-19 09:03:45 -04:00
wip: default rotation
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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[]>
|
||||
}
|
||||
+2
-1
@@ -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>
|
||||
|
||||
+12
-3
@@ -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>
|
||||
}
|
||||
|
||||
+13
-4
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user