feat(workspace): accepting invitation

This commit is contained in:
Karol Sójko
2022-10-11 10:01:13 +02:00
parent 712e874bfe
commit ace2b6936a
7 changed files with 148 additions and 0 deletions
@@ -1,5 +1,8 @@
import { Uuid } from '@standardnotes/common'
import { WorkspaceInvite } from './WorkspaceInvite'
export interface WorkspaceInviteRepositoryInterface {
findOneByUuid(uuid: Uuid): Promise<WorkspaceInvite | null>
save(workspace: WorkspaceInvite): Promise<WorkspaceInvite>
}
@@ -0,0 +1,71 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { WorkspaceInvite } from '../../Invite/WorkspaceInvite'
import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { AcceptInvitation } from './AcceptInvitation'
describe('AcceptInvitation', () => {
let workspaceInviteRepository: WorkspaceInviteRepositoryInterface
let workspaceUserRepository: WorkspaceUserRepositoryInterface
let timer: TimerInterface
let invite: WorkspaceInvite
const createUseCase = () => new AcceptInvitation(workspaceInviteRepository, workspaceUserRepository, timer)
beforeEach(() => {
invite = {
uuid: 'i-1-2-3',
workspaceUuid: 'w-1-2-3',
} as jest.Mocked<WorkspaceInvite>
workspaceInviteRepository = {} as jest.Mocked<WorkspaceInviteRepositoryInterface>
workspaceInviteRepository.findOneByUuid = jest.fn().mockReturnValue(invite)
workspaceInviteRepository.save = jest.fn()
workspaceUserRepository = {} as jest.Mocked<WorkspaceUserRepositoryInterface>
workspaceUserRepository.save = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
})
it('should accept an invite and assign user to workspace', async () => {
await createUseCase().execute({
acceptingUserUuid: 'u-1-2-3',
encryptedPrivateKey: 'foo',
publicKey: 'bar',
invitationUuid: 'i-1-2-3',
})
expect(workspaceInviteRepository.save).toHaveBeenCalledWith({
acceptingUserUuid: 'u-1-2-3',
status: 'accepted',
updatedAt: 1,
uuid: 'i-1-2-3',
workspaceUuid: 'w-1-2-3',
})
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
encryptedPrivateKey: 'foo',
publicKey: 'bar',
status: 'pending-keyshare',
userUuid: 'u-1-2-3',
workspaceUuid: 'w-1-2-3',
})
})
it('should not accept an invite if it does not exist', async () => {
workspaceInviteRepository.findOneByUuid = jest.fn().mockReturnValue(null)
await createUseCase().execute({
acceptingUserUuid: 'u-1-2-3',
encryptedPrivateKey: 'foo',
publicKey: 'bar',
invitationUuid: 'i-1-2-3',
})
expect(workspaceInviteRepository.save).not.toHaveBeenCalled()
expect(workspaceUserRepository.save).not.toHaveBeenCalled()
})
})
@@ -0,0 +1,50 @@
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface'
import { WorkspaceInviteStatus } from '../../Invite/WorkspaceInviteStatus'
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { WorkspaceUserStatus } from '../../Workspace/WorkspaceUserStatus'
import { UseCaseInterface } from '../UseCaseInterface'
import { AcceptInvitationDTO } from './AcceptInvitationDTO'
import { AcceptInvitationResponse } from './AcceptInvitationResponse'
@injectable()
export class AcceptInvitation implements UseCaseInterface {
constructor(
@inject(TYPES.WorkspaceInviteRepository) private workspaceInviteRepository: WorkspaceInviteRepositoryInterface,
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
) {}
async execute(dto: AcceptInvitationDTO): Promise<AcceptInvitationResponse> {
const invite = await this.workspaceInviteRepository.findOneByUuid(dto.invitationUuid)
if (invite === null) {
return {
success: false,
}
}
invite.acceptingUserUuid = dto.acceptingUserUuid
invite.updatedAt = this.timer.getTimestampInMicroseconds()
invite.status = WorkspaceInviteStatus.Accepted
await this.workspaceInviteRepository.save(invite)
const workspaceUser = new WorkspaceUser()
workspaceUser.userUuid = dto.acceptingUserUuid
workspaceUser.workspaceUuid = invite.workspaceUuid
workspaceUser.publicKey = dto.publicKey
workspaceUser.encryptedPrivateKey = dto.encryptedPrivateKey
workspaceUser.status = WorkspaceUserStatus.PendingKeyshare
await this.workspaceUserRepository.save(workspaceUser)
return {
success: true,
}
}
}
@@ -0,0 +1,8 @@
import { Uuid } from '@standardnotes/common'
export type AcceptInvitationDTO = {
invitationUuid: Uuid
acceptingUserUuid: Uuid
publicKey: string
encryptedPrivateKey: string
}
@@ -0,0 +1,3 @@
export type AcceptInvitationResponse = {
success: boolean
}
@@ -27,4 +27,13 @@ describe('MySQLWorkspaceInviteRepository', () => {
expect(ormRepository.save).toHaveBeenCalledWith(invite)
})
it('should find one by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(null)
await createRepository().findOneByUuid('i-1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', { uuid: 'i-1-2-3' })
})
})
@@ -12,6 +12,10 @@ export class MySQLWorkspaceInviteRepository implements WorkspaceInviteRepository
private ormRepository: Repository<WorkspaceInvite>,
) {}
async findOneByUuid(uuid: string): Promise<WorkspaceInvite | null> {
return this.ormRepository.createQueryBuilder().where('uuid = :uuid', { uuid }).getOne()
}
async save(workspaceInvite: WorkspaceInvite): Promise<WorkspaceInvite> {
return this.ormRepository.save(workspaceInvite)
}