feat(workspace): add inviting to workspace

This commit is contained in:
Karol Sójko
2022-10-10 11:38:59 +02:00
parent 8a72a1a559
commit e06cc3ba80
20 changed files with 325 additions and 5 deletions
@@ -0,0 +1,68 @@
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
import { Workspace } from '../Workspace/Workspace'
import { WorkspaceInviteStatus } from './WorkspaceInviteStatus'
@Entity({ name: 'workspace_invites' })
export class WorkspaceInvite {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'inviter_uuid',
length: 36,
})
declare inviterUuid: string
@Column({
name: 'invitee_email',
length: 255,
})
declare inviteeEmail: string
@Column({
name: 'status',
type: 'varchar',
length: 64,
})
declare status: WorkspaceInviteStatus
@Column({
name: 'accepting_user_uuid',
type: 'varchar',
length: 36,
nullable: true,
})
declare acceptingUserUuid: string | null
@Column({
name: 'workspace_uuid',
length: 36,
})
declare workspaceUuid: string
@Column({
name: 'created_at',
type: 'bigint',
})
declare createdAt: number
@Column({
name: 'updated_at',
type: 'bigint',
})
declare updatedAt: number
@ManyToOne(
/* istanbul ignore next */
() => Workspace,
/* istanbul ignore next */
(workspace) => workspace.invites,
/* istanbul ignore next */
{ onDelete: 'CASCADE' },
)
@JoinColumn(
/* istanbul ignore next */
{ name: 'workspace_uuid' },
)
declare workspace: Promise<Workspace>
}
@@ -0,0 +1,5 @@
import { WorkspaceInvite } from './WorkspaceInvite'
export interface WorkspaceInviteRepositoryInterface {
save(workspace: WorkspaceInvite): Promise<WorkspaceInvite>
}
@@ -0,0 +1,4 @@
export enum WorkspaceInviteStatus {
Created = 'created',
Accepted = 'accepted',
}
@@ -6,14 +6,19 @@ import { WorkspaceRepositoryInterface } from '../../Workspace/WorkspaceRepositor
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { CreateWorkspace } from './CreateWorkspace'
import { TimerInterface } from '@standardnotes/time'
describe('CreateWorkspace', () => {
let workspaceRepository: WorkspaceRepositoryInterface
let workspaceUserRepository: WorkspaceUserRepositoryInterface
let timer: TimerInterface
const createUseCase = () => new CreateWorkspace(workspaceRepository, workspaceUserRepository)
const createUseCase = () => new CreateWorkspace(workspaceRepository, workspaceUserRepository, timer)
beforeEach(() => {
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
workspaceRepository = {} as jest.Mocked<WorkspaceRepositoryInterface>
workspaceRepository.save = jest.fn().mockImplementation((workspace) => {
return {
@@ -39,6 +44,8 @@ describe('CreateWorkspace', () => {
expect(workspaceRepository.save).toHaveBeenCalledWith({
name: 'A Team',
type: 'root',
createdAt: 1,
updatedAt: 1,
})
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
accessLevel: 'owner',
@@ -48,6 +55,8 @@ describe('CreateWorkspace', () => {
status: 'active',
userUuid: '1-2-3',
workspaceUuid: 'w-1-2-3',
createdAt: 1,
updatedAt: 1,
})
})
@@ -59,12 +68,16 @@ describe('CreateWorkspace', () => {
expect(workspaceRepository.save).toHaveBeenCalledWith({
type: 'private',
createdAt: 1,
updatedAt: 1,
})
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
accessLevel: 'owner',
status: 'active',
userUuid: '1-2-3',
workspaceUuid: 'w-1-2-3',
createdAt: 1,
updatedAt: 1,
})
})
})
@@ -1,3 +1,4 @@
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
@@ -17,6 +18,7 @@ export class CreateWorkspace implements UseCaseInterface {
constructor(
@inject(TYPES.WorkspaceRepository) private workspaceRepository: WorkspaceRepositoryInterface,
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
) {}
async execute(dto: CreateWorkspaceDTO): Promise<CreateWorkspaceResponse> {
@@ -25,6 +27,9 @@ export class CreateWorkspace implements UseCaseInterface {
workspace.name = dto.name
}
workspace.type = dto.type
const timestamp = this.timer.getTimestampInMicroseconds()
workspace.createdAt = timestamp
workspace.updatedAt = timestamp
workspace = await this.workspaceRepository.save(workspace)
@@ -42,6 +47,8 @@ export class CreateWorkspace implements UseCaseInterface {
ownerAssociation.status = WorkspaceUserStatus.Active
ownerAssociation.userUuid = dto.ownerUuid
ownerAssociation.workspaceUuid = workspace.uuid
ownerAssociation.createdAt = timestamp
ownerAssociation.updatedAt = timestamp
await this.workspaceUserRepository.save(ownerAssociation)
@@ -0,0 +1,45 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface'
import { InviteToWorkspace } from './InviteToWorkspace'
describe('InviteToWorkspace', () => {
let workspaceInviteRepository: WorkspaceInviteRepositoryInterface
let timer: TimerInterface
const createUseCase = () => new InviteToWorkspace(workspaceInviteRepository, timer)
beforeEach(() => {
workspaceInviteRepository = {} as jest.Mocked<WorkspaceInviteRepositoryInterface>
workspaceInviteRepository.save = jest.fn().mockImplementation((invite) => {
return {
...invite,
uuid: 'i-1-2-3',
}
})
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
})
it('should create an invite', async () => {
const result = await createUseCase().execute({
inviteeEmail: '[email protected]',
inviterUuid: 'u-1-2-3',
workspaceUuid: 'w-1-2-3',
})
expect(result).toEqual({ uuid: 'i-1-2-3' })
expect(workspaceInviteRepository.save).toHaveBeenCalledWith({
inviterUuid: 'u-1-2-3',
inviteeEmail: '[email protected]',
workspaceUuid: 'w-1-2-3',
status: 'created',
createdAt: 1,
updatedAt: 1,
})
})
})
@@ -0,0 +1,37 @@
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { WorkspaceInvite } from '../../Invite/WorkspaceInvite'
import { WorkspaceInviteRepositoryInterface } from '../../Invite/WorkspaceInviteRepositoryInterface'
import { WorkspaceInviteStatus } from '../../Invite/WorkspaceInviteStatus'
import { UseCaseInterface } from '../UseCaseInterface'
import { InviteToWorkspaceDTO } from './InviteToWorkspaceDTO'
import { InviteToWorkspaceResponse } from './InviteToWorkspaceResponse'
@injectable()
export class InviteToWorkspace implements UseCaseInterface {
constructor(
@inject(TYPES.WorkspaceInviteRepository) private workspaceInviteRepository: WorkspaceInviteRepositoryInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
) {}
async execute(dto: InviteToWorkspaceDTO): Promise<InviteToWorkspaceResponse> {
let invite = new WorkspaceInvite()
invite.inviterUuid = dto.inviterUuid
invite.inviteeEmail = dto.inviteeEmail
invite.workspaceUuid = dto.workspaceUuid
invite.status = WorkspaceInviteStatus.Created
const timestamp = this.timer.getTimestampInMicroseconds()
invite.createdAt = timestamp
invite.updatedAt = timestamp
invite = await this.workspaceInviteRepository.save(invite)
return {
uuid: invite.uuid,
}
}
}
@@ -0,0 +1,7 @@
import { Uuid } from '@standardnotes/common'
export type InviteToWorkspaceDTO = {
workspaceUuid: Uuid
inviterUuid: Uuid
inviteeEmail: string
}
@@ -0,0 +1,3 @@
export type InviteToWorkspaceResponse = {
uuid: string
}
@@ -1,5 +1,6 @@
import { WorkspaceType } from '@standardnotes/common'
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'
import { WorkspaceInvite } from '../Invite/WorkspaceInvite'
@Entity({ name: 'workspaces' })
export class Workspace {
@@ -23,4 +24,24 @@ export class Workspace {
default: 0,
})
declare keyRotationIndex: number
@Column({
name: 'created_at',
type: 'bigint',
})
declare createdAt: number
@Column({
name: 'updated_at',
type: 'bigint',
})
declare updatedAt: number
@OneToMany(
/* istanbul ignore next */
() => WorkspaceInvite,
/* istanbul ignore next */
(workspaceInvite) => workspaceInvite.workspace,
)
declare invites: Promise<WorkspaceInvite[]>
}
@@ -61,4 +61,16 @@ export class WorkspaceUser {
default: 0,
})
declare keyRotationIndex: number
@Column({
name: 'created_at',
type: 'bigint',
})
declare createdAt: number
@Column({
name: 'updated_at',
type: 'bigint',
})
declare updatedAt: number
}