diff --git a/.pnp.cjs b/.pnp.cjs index 10e4381ac..ef81aa03e 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -3218,6 +3218,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ ["@standardnotes/security", "workspace:packages/security"],\ + ["@standardnotes/time", "workspace:packages/time"],\ ["@types/cors", "npm:2.8.12"],\ ["@types/express", "npm:4.17.13"],\ ["@types/ioredis", "npm:4.28.10"],\ diff --git a/packages/workspace/migrations/1665394559520-add-invites.ts b/packages/workspace/migrations/1665394559520-add-invites.ts new file mode 100644 index 000000000..4b0268525 --- /dev/null +++ b/packages/workspace/migrations/1665394559520-add-invites.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class addInvites1665394559520 implements MigrationInterface { + name = 'addInvites1665394559520' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE TABLE `workspace_invites` (`uuid` varchar(36) NOT NULL, `inviter_uuid` varchar(36) NOT NULL, `invitee_email` varchar(255) NOT NULL, `status` varchar(64) NOT NULL, `accepting_user_uuid` varchar(36) NULL, `workspace_uuid` varchar(36) NOT NULL, `created_at` bigint NOT NULL, `updated_at` bigint NOT NULL, PRIMARY KEY (`uuid`)) ENGINE=InnoDB', + ) + await queryRunner.query('ALTER TABLE `workspaces` ADD `created_at` bigint NOT NULL') + await queryRunner.query('ALTER TABLE `workspaces` ADD `updated_at` bigint NOT NULL') + await queryRunner.query('ALTER TABLE `workspace_users` ADD `created_at` bigint NOT NULL') + await queryRunner.query('ALTER TABLE `workspace_users` ADD `updated_at` bigint NOT NULL') + await queryRunner.query( + 'ALTER TABLE `workspace_invites` ADD CONSTRAINT `FK_782df40d03151dd3998acd0a6ba` FOREIGN KEY (`workspace_uuid`) REFERENCES `workspaces`(`uuid`) ON DELETE CASCADE ON UPDATE NO ACTION', + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('ALTER TABLE `workspace_invites` DROP FOREIGN KEY `FK_782df40d03151dd3998acd0a6ba`') + await queryRunner.query('ALTER TABLE `workspace_users` DROP COLUMN `updated_at`') + await queryRunner.query('ALTER TABLE `workspace_users` DROP COLUMN `created_at`') + await queryRunner.query('ALTER TABLE `workspaces` DROP COLUMN `updated_at`') + await queryRunner.query('ALTER TABLE `workspaces` DROP COLUMN `created_at`') + await queryRunner.query('DROP TABLE `workspace_invites`') + } +} diff --git a/packages/workspace/package.json b/packages/workspace/package.json index 77196d0e6..cd0d6d651 100644 --- a/packages/workspace/package.json +++ b/packages/workspace/package.json @@ -30,6 +30,7 @@ "@standardnotes/domain-events": "workspace:^", "@standardnotes/domain-events-infra": "workspace:^", "@standardnotes/security": "workspace:*", + "@standardnotes/time": "workspace:^", "aws-sdk": "^2.1159.0", "cors": "2.8.5", "dotenv": "^16.0.1", diff --git a/packages/workspace/src/Bootstrap/Container.ts b/packages/workspace/src/Bootstrap/Container.ts index debebf788..210db3477 100644 --- a/packages/workspace/src/Bootstrap/Container.ts +++ b/packages/workspace/src/Bootstrap/Container.ts @@ -7,6 +7,7 @@ import { DomainEventMessageHandlerInterface, DomainEventSubscriberFactoryInterface, } from '@standardnotes/domain-events' +import { TimerInterface, Timer } from '@standardnotes/time' import { Env } from './Env' import TYPES from './Types' import { AppDataSource } from './DataSource' @@ -31,6 +32,10 @@ import { WorkspaceUser } from '../Domain/Workspace/WorkspaceUser' import { CreateWorkspace } from '../Domain/UseCase/CreateWorkspace/CreateWorkspace' import { WorkspacesController } from '../Controller/WorkspacesController' import { UserRegisteredEventHandler } from '../Domain/Handler/UserRegisteredEventHandler' +import { WorkspaceInviteRepositoryInterface } from '../Domain/Invite/WorkspaceInviteRepositoryInterface' +import { MySQLWorkspaceInviteRepository } from '../Infra/MySQL/MySQLWorkspaceInviteRepository' +import { WorkspaceInvite } from '../Domain/Invite/WorkspaceInvite' +import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace' // eslint-disable-next-line @typescript-eslint/no-var-requires const newrelicFormatter = require('@newrelic/winston-enricher') @@ -96,6 +101,9 @@ export class ContainerConfigLoader { // Repositories container.bind(TYPES.WorkspaceRepository).to(MySQLWorkspaceRepository) container.bind(TYPES.WorkspaceUserRepository).to(MySQLWorkspaceUserRepository) + container + .bind(TYPES.WorkspaceInviteRepository) + .to(MySQLWorkspaceInviteRepository) // ORM container .bind>(TYPES.ORMWorkspaceRepository) @@ -103,6 +111,9 @@ export class ContainerConfigLoader { container .bind>(TYPES.ORMWorkspaceUserRepository) .toConstantValue(AppDataSource.getRepository(WorkspaceUser)) + container + .bind>(TYPES.ORMWorkspaceInviteRepository) + .toConstantValue(AppDataSource.getRepository(WorkspaceInvite)) // Middleware container.bind(TYPES.ApiGatewayAuthMiddleware).to(ApiGatewayAuthMiddleware) // env vars @@ -117,9 +128,11 @@ export class ContainerConfigLoader { // use cases container.bind(TYPES.CreateWorkspace).to(CreateWorkspace) + container.bind(TYPES.InviteToWorkspace).to(InviteToWorkspace) // Handlers container.bind(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler) // Services + container.bind(TYPES.Timer).toConstantValue(new Timer()) container .bind>(TYPES.CrossServiceTokenDecoder) .toConstantValue(new TokenDecoder(container.get(TYPES.AUTH_JWT_SECRET))) @@ -136,7 +149,9 @@ export class ContainerConfigLoader { ) } - const eventHandlers: Map = new Map([]) + const eventHandlers: Map = new Map([ + ['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)], + ]) if (env.get('SQS_QUEUE_URL', true)) { container diff --git a/packages/workspace/src/Bootstrap/DataSource.ts b/packages/workspace/src/Bootstrap/DataSource.ts index c23bf2f75..ce87e61d9 100644 --- a/packages/workspace/src/Bootstrap/DataSource.ts +++ b/packages/workspace/src/Bootstrap/DataSource.ts @@ -1,4 +1,5 @@ import { DataSource, LoggerOptions } from 'typeorm' +import { WorkspaceInvite } from '../Domain/Invite/WorkspaceInvite' import { Workspace } from '../Domain/Workspace/Workspace' import { WorkspaceUser } from '../Domain/Workspace/WorkspaceUser' import { Env } from './Env' @@ -34,7 +35,7 @@ export const AppDataSource = new DataSource({ ], removeNodeErrorCount: 10, }, - entities: [Workspace, WorkspaceUser], + entities: [Workspace, WorkspaceUser, WorkspaceInvite], migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'], migrationsRun: true, logging: env.get('DB_DEBUG_LEVEL'), diff --git a/packages/workspace/src/Bootstrap/Types.ts b/packages/workspace/src/Bootstrap/Types.ts index eb1c7200e..119a7b946 100644 --- a/packages/workspace/src/Bootstrap/Types.ts +++ b/packages/workspace/src/Bootstrap/Types.ts @@ -8,9 +8,11 @@ const TYPES = { // Repositories WorkspaceRepository: Symbol.for('WorkspaceRepository'), WorkspaceUserRepository: Symbol.for('WorkspaceUserRepository'), + WorkspaceInviteRepository: Symbol.for('WorkspaceInviteRepository'), // ORM ORMWorkspaceRepository: Symbol.for('ORMWorkspaceRepository'), ORMWorkspaceUserRepository: Symbol.for('ORMWorkspaceUserRepository'), + ORMWorkspaceInviteRepository: Symbol.for('ORMWorkspaceInviteRepository'), // Middleware ApiGatewayAuthMiddleware: Symbol.for('ApiGatewayAuthMiddleware'), // env vars @@ -25,9 +27,11 @@ const TYPES = { VERSION: Symbol.for('VERSION'), // use cases CreateWorkspace: Symbol.for('CreateWorkspace'), + InviteToWorkspace: Symbol.for('InviteToWorkspace'), // Handlers UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'), // Services + Timer: Symbol.for('Timer'), CrossServiceTokenDecoder: Symbol.for('CrossServiceTokenDecoder'), DomainEventPublisher: Symbol.for('DomainEventPublisher'), DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'), diff --git a/packages/workspace/src/Domain/Invite/WorkspaceInvite.ts b/packages/workspace/src/Domain/Invite/WorkspaceInvite.ts new file mode 100644 index 000000000..03005769b --- /dev/null +++ b/packages/workspace/src/Domain/Invite/WorkspaceInvite.ts @@ -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 +} diff --git a/packages/workspace/src/Domain/Invite/WorkspaceInviteRepositoryInterface.ts b/packages/workspace/src/Domain/Invite/WorkspaceInviteRepositoryInterface.ts new file mode 100644 index 000000000..40625a642 --- /dev/null +++ b/packages/workspace/src/Domain/Invite/WorkspaceInviteRepositoryInterface.ts @@ -0,0 +1,5 @@ +import { WorkspaceInvite } from './WorkspaceInvite' + +export interface WorkspaceInviteRepositoryInterface { + save(workspace: WorkspaceInvite): Promise +} diff --git a/packages/workspace/src/Domain/Invite/WorkspaceInviteStatus.ts b/packages/workspace/src/Domain/Invite/WorkspaceInviteStatus.ts new file mode 100644 index 000000000..fb9fbc79e --- /dev/null +++ b/packages/workspace/src/Domain/Invite/WorkspaceInviteStatus.ts @@ -0,0 +1,4 @@ +export enum WorkspaceInviteStatus { + Created = 'created', + Accepted = 'accepted', +} diff --git a/packages/workspace/src/Domain/UseCase/CreateWorkspace/CreateWorkspace.spec.ts b/packages/workspace/src/Domain/UseCase/CreateWorkspace/CreateWorkspace.spec.ts index d0880c5e5..a10adbcb1 100644 --- a/packages/workspace/src/Domain/UseCase/CreateWorkspace/CreateWorkspace.spec.ts +++ b/packages/workspace/src/Domain/UseCase/CreateWorkspace/CreateWorkspace.spec.ts @@ -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 + timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1) + workspaceRepository = {} as jest.Mocked 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, }) }) }) diff --git a/packages/workspace/src/Domain/UseCase/CreateWorkspace/CreateWorkspace.ts b/packages/workspace/src/Domain/UseCase/CreateWorkspace/CreateWorkspace.ts index 93fcc04a5..932e38583 100644 --- a/packages/workspace/src/Domain/UseCase/CreateWorkspace/CreateWorkspace.ts +++ b/packages/workspace/src/Domain/UseCase/CreateWorkspace/CreateWorkspace.ts @@ -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 { @@ -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) diff --git a/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.spec.ts b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.spec.ts new file mode 100644 index 000000000..127f9fef6 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.spec.ts @@ -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 + workspaceInviteRepository.save = jest.fn().mockImplementation((invite) => { + return { + ...invite, + uuid: 'i-1-2-3', + } + }) + + timer = {} as jest.Mocked + timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1) + }) + + it('should create an invite', async () => { + const result = await createUseCase().execute({ + inviteeEmail: 'test@test.te', + 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: 'test@test.te', + workspaceUuid: 'w-1-2-3', + status: 'created', + createdAt: 1, + updatedAt: 1, + }) + }) +}) diff --git a/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.ts b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.ts new file mode 100644 index 000000000..60fad4b14 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspace.ts @@ -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 { + 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, + } + } +} diff --git a/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspaceDTO.ts b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspaceDTO.ts new file mode 100644 index 000000000..0c52df4d4 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspaceDTO.ts @@ -0,0 +1,7 @@ +import { Uuid } from '@standardnotes/common' + +export type InviteToWorkspaceDTO = { + workspaceUuid: Uuid + inviterUuid: Uuid + inviteeEmail: string +} diff --git a/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspaceResponse.ts b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspaceResponse.ts new file mode 100644 index 000000000..64a060c07 --- /dev/null +++ b/packages/workspace/src/Domain/UseCase/InviteToWorkspace/InviteToWorkspaceResponse.ts @@ -0,0 +1,3 @@ +export type InviteToWorkspaceResponse = { + uuid: string +} diff --git a/packages/workspace/src/Domain/Workspace/Workspace.ts b/packages/workspace/src/Domain/Workspace/Workspace.ts index 6ce21340c..30a3e5a21 100644 --- a/packages/workspace/src/Domain/Workspace/Workspace.ts +++ b/packages/workspace/src/Domain/Workspace/Workspace.ts @@ -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 } diff --git a/packages/workspace/src/Domain/Workspace/WorkspaceUser.ts b/packages/workspace/src/Domain/Workspace/WorkspaceUser.ts index adaab84c1..2b546d249 100644 --- a/packages/workspace/src/Domain/Workspace/WorkspaceUser.ts +++ b/packages/workspace/src/Domain/Workspace/WorkspaceUser.ts @@ -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 } diff --git a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceInviteRepository.spec.ts b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceInviteRepository.spec.ts new file mode 100644 index 000000000..60c2c2409 --- /dev/null +++ b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceInviteRepository.spec.ts @@ -0,0 +1,30 @@ +import 'reflect-metadata' + +import { Repository, SelectQueryBuilder } from 'typeorm' + +import { WorkspaceInvite } from '../../Domain/Invite/WorkspaceInvite' +import { MySQLWorkspaceInviteRepository } from './MySQLWorkspaceInviteRepository' + +describe('MySQLWorkspaceInviteRepository', () => { + let ormRepository: Repository + let invite: WorkspaceInvite + let queryBuilder: SelectQueryBuilder + + const createRepository = () => new MySQLWorkspaceInviteRepository(ormRepository) + + beforeEach(() => { + invite = {} as jest.Mocked + + queryBuilder = {} as jest.Mocked> + + ormRepository = {} as jest.Mocked> + ormRepository.save = jest.fn() + ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder) + }) + + it('should save', async () => { + await createRepository().save(invite) + + expect(ormRepository.save).toHaveBeenCalledWith(invite) + }) +}) diff --git a/packages/workspace/src/Infra/MySQL/MySQLWorkspaceInviteRepository.ts b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceInviteRepository.ts new file mode 100644 index 000000000..9f722f134 --- /dev/null +++ b/packages/workspace/src/Infra/MySQL/MySQLWorkspaceInviteRepository.ts @@ -0,0 +1,18 @@ +import { inject, injectable } from 'inversify' +import { Repository } from 'typeorm' + +import TYPES from '../../Bootstrap/Types' +import { WorkspaceInvite } from '../../Domain/Invite/WorkspaceInvite' +import { WorkspaceInviteRepositoryInterface } from '../../Domain/Invite/WorkspaceInviteRepositoryInterface' + +@injectable() +export class MySQLWorkspaceInviteRepository implements WorkspaceInviteRepositoryInterface { + constructor( + @inject(TYPES.ORMWorkspaceInviteRepository) + private ormRepository: Repository, + ) {} + + async save(workspaceInvite: WorkspaceInvite): Promise { + return this.ormRepository.save(workspaceInvite) + } +} diff --git a/yarn.lock b/yarn.lock index 6c0e1890e..9142f012b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2397,7 +2397,7 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/time@workspace:*, @standardnotes/time@workspace:packages/time": +"@standardnotes/time@workspace:*, @standardnotes/time@workspace:^, @standardnotes/time@workspace:packages/time": version: 0.0.0-use.local resolution: "@standardnotes/time@workspace:packages/time" dependencies: @@ -2447,6 +2447,7 @@ __metadata: "@standardnotes/domain-events": "workspace:^" "@standardnotes/domain-events-infra": "workspace:^" "@standardnotes/security": "workspace:*" + "@standardnotes/time": "workspace:^" "@types/cors": ^2.8.9 "@types/express": ^4.17.11 "@types/ioredis": ^4.28.10