Compare commits

..

4 Commits

24 changed files with 370 additions and 30 deletions

35
.pnp.cjs generated
View File

@@ -2521,13 +2521,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/api", [\
["npm:1.15.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.15.0-12a67ff9b7-88ae0a340e.zip/node_modules/@standardnotes/api/",\
["npm:1.16.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.16.0-efccf518ba-465f76dd29.zip/node_modules/@standardnotes/api/",\
"packageDependencies": [\
["@standardnotes/api", "npm:1.15.0"],\
["@standardnotes/api", "npm:1.16.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/encryption", "npm:1.17.0"],\
["@standardnotes/models", "npm:1.26.0"],\
["@standardnotes/encryption", "npm:1.17.1"],\
["@standardnotes/models", "npm:1.27.0"],\
["@standardnotes/responses", "npm:1.11.0"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/utils", "npm:1.10.0"],\
@@ -2600,7 +2600,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["@sentry/node", "npm:7.5.0"],\
["@standardnotes/analytics", "workspace:packages/analytics"],\
["@standardnotes/api", "npm:1.15.0"],\
["@standardnotes/api", "npm:1.16.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
@@ -2725,12 +2725,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/encryption", [\
["npm:1.17.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.17.0-587d631df2-587516dfed.zip/node_modules/@standardnotes/encryption/",\
["npm:1.17.1", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.17.1-f4d1330273-2b2408ffbd.zip/node_modules/@standardnotes/encryption/",\
"packageDependencies": [\
["@standardnotes/encryption", "npm:1.17.0"],\
["@standardnotes/encryption", "npm:1.17.1"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/models", "npm:1.26.0"],\
["@standardnotes/models", "npm:1.27.0"],\
["@standardnotes/responses", "npm:1.11.0"],\
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
["@standardnotes/utils", "npm:1.10.0"],\
@@ -2868,6 +2868,19 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}],\
["npm:1.27.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.27.0-831bd645c6-263fd9e923.zip/node_modules/@standardnotes/models/",\
"packageDependencies": [\
["@standardnotes/models", "npm:1.27.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.53.0"],\
["@standardnotes/responses", "npm:1.11.0"],\
["@standardnotes/utils", "npm:1.10.0"],\
["lodash", "npm:4.17.21"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}]\
]],\
["@standardnotes/payloads", [\
@@ -3154,7 +3167,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@standardnotes/workspace-server", "workspace:packages/workspace"],\
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["@sentry/node", "npm:7.5.0"],\
["@standardnotes/api", "npm:1.15.0"],\
["@standardnotes/api", "npm:1.16.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.30.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.29.0...@standardnotes/api-gateway@1.30.0) (2022-10-12)
### Features
* **workspace:** add endpoints for initiating keyshare in a workspace ([0c1a779](https://github.com/standardnotes/api-gateway/commit/0c1a779ef03819928e7e791a6843d90eb9fed964))
# [1.29.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.28.2...@standardnotes/api-gateway@1.29.0) (2022-10-11)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.29.0",
"version": "1.30.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -26,6 +26,16 @@ export class WorkspacesController extends BaseHttpController {
)
}
@httpPost('/:workspaceUuid/users/:userUuid/keyshare')
async initiateKeyshare(request: Request, response: Response): Promise<void> {
await this.httpService.callWorkspaceServer(
request,
response,
`workspaces/${request.params.workspaceUuid}/users/${request.params.userUuid}/keyshare`,
request.body,
)
}
@httpGet('/')
async listWorkspaces(request: Request, response: Response): Promise<void> {
await this.httpService.callWorkspaceServer(request, response, 'workspaces', request.body)

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.43.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.42.0...@standardnotes/auth-server@1.43.0) (2022-10-12)
### Features
* **workspace:** add endpoints for initiating keyshare in a workspace ([0c1a779](https://github.com/standardnotes/server/commit/0c1a779ef03819928e7e791a6843d90eb9fed964))
# [1.42.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.41.2...@standardnotes/auth-server@1.42.0) (2022-10-11)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.42.0",
"version": "1.43.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -34,7 +34,7 @@
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.3.0",
"@standardnotes/analytics": "workspace:*",
"@standardnotes/api": "^1.15.0",
"@standardnotes/api": "^1.16.0",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.12.0...@standardnotes/workspace-server@1.13.0) (2022-10-12)
### Features
* **workspace:** add endpoints for initiating keyshare in a workspace ([0c1a779](https://github.com/standardnotes/server/commit/0c1a779ef03819928e7e791a6843d90eb9fed964))
# [1.12.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.11.0...@standardnotes/workspace-server@1.12.0) (2022-10-12)
### Features
* **workspace:** add initiating key share ([cea9021](https://github.com/standardnotes/server/commit/cea9021c164588969890370a2332f11749ac820e))
# [1.11.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.10.0...@standardnotes/workspace-server@1.11.0) (2022-10-11)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/workspace-server",
"version": "1.11.0",
"version": "1.13.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -25,7 +25,7 @@
"dependencies": {
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.3.0",
"@standardnotes/api": "^1.15.0",
"@standardnotes/api": "^1.16.0",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-events": "workspace:^",
"@standardnotes/domain-events-infra": "workspace:^",

View File

@@ -46,6 +46,7 @@ import { WorkspaceUserProjector } from '../Domain/Projection/WorkspaceUserProjec
import { AcceptInvitation } from '../Domain/UseCase/AcceptInvitation/AcceptInvitation'
import { ListWorkspaces } from '../Domain/UseCase/ListWorkspaces/ListWorkspaces'
import { ListWorkspaceUsers } from '../Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers'
import { InitiateKeyShare } from '../Domain/UseCase/InitiateKeyShare/InitiateKeyShare'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -142,6 +143,7 @@ export class ContainerConfigLoader {
container.bind<AcceptInvitation>(TYPES.AcceptInvitation).to(AcceptInvitation)
container.bind<ListWorkspaces>(TYPES.ListWorkspaces).to(ListWorkspaces)
container.bind<ListWorkspaceUsers>(TYPES.ListWorkspaceUsers).to(ListWorkspaceUsers)
container.bind<InitiateKeyShare>(TYPES.InitiateKeyShare).to(InitiateKeyShare)
// Handlers
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
// Projection

View File

@@ -31,6 +31,7 @@ const TYPES = {
AcceptInvitation: Symbol.for('AcceptInvitation'),
ListWorkspaces: Symbol.for('ListWorkspaces'),
ListWorkspaceUsers: Symbol.for('ListWorkspaceUsers'),
InitiateKeyShare: Symbol.for('InitiateKeyShare'),
// Handlers
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
// Projection

View File

@@ -6,6 +6,7 @@ import { WorkspaceUserProjection } from '../Domain/Projection/WorkspaceUserProje
import { AcceptInvitation } from '../Domain/UseCase/AcceptInvitation/AcceptInvitation'
import { CreateWorkspace } from '../Domain/UseCase/CreateWorkspace/CreateWorkspace'
import { InitiateKeyShare } from '../Domain/UseCase/InitiateKeyShare/InitiateKeyShare'
import { InviteToWorkspace } from '../Domain/UseCase/InviteToWorkspace/InviteToWorkspace'
import { ListWorkspaces } from '../Domain/UseCase/ListWorkspaces/ListWorkspaces'
import { ListWorkspaceUsers } from '../Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers'
@@ -20,6 +21,7 @@ describe('WorkspacesController', () => {
let doAcceptInvitation: AcceptInvitation
let doListWorkspaces: ListWorkspaces
let doListWorkspaceUsers: ListWorkspaceUsers
let doInitiateKeyshare: InitiateKeyShare
let workspacesProject: ProjectorInterface<Workspace, WorkspaceProjection>
let workspaceUsersProjector: ProjectorInterface<WorkspaceUser, WorkspaceUserProjection>
let workspace1: Workspace
@@ -34,6 +36,7 @@ describe('WorkspacesController', () => {
doListWorkspaces,
doListWorkspaceUsers,
doAcceptInvitation,
doInitiateKeyshare,
workspacesProject,
workspaceUsersProjector,
)
@@ -56,6 +59,9 @@ describe('WorkspacesController', () => {
doAcceptInvitation = {} as jest.Mocked<AcceptInvitation>
doAcceptInvitation.execute = jest.fn().mockReturnValue({ success: true })
doInitiateKeyshare = {} as jest.Mocked<InitiateKeyShare>
doInitiateKeyshare.execute = jest.fn().mockReturnValue({ success: true })
workspacesProject = {} as jest.Mocked<ProjectorInterface<Workspace, WorkspaceProjection>>
workspacesProject.project = jest.fn().mockReturnValue({ foo: 'bar' })
@@ -158,4 +164,40 @@ describe('WorkspacesController', () => {
status: 200,
})
})
it('should initiate keyshare', async () => {
const result = await createController().initiateKeyshare({
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foo',
workspaceUuid: 'w-1-2-3',
performingUserUuid: 'p-1-2-3',
})
expect(result).toEqual({
data: {
success: true,
},
status: 200,
})
})
it('should not initiate keyshare if it fails', async () => {
doInitiateKeyshare.execute = jest.fn().mockReturnValue({ success: false })
const result = await createController().initiateKeyshare({
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foo',
workspaceUuid: 'w-1-2-3',
performingUserUuid: 'p-1-2-3',
})
expect(result).toEqual({
data: {
error: {
message: 'Could not initiate keyshare.',
},
},
status: 400,
})
})
})

View File

@@ -11,6 +11,8 @@ import {
WorkspaceInvitationAcceptingRequestParams,
WorkspaceInvitationAcceptingResponse,
WorkspaceUserListRequestParams,
WorkspaceKeyshareInitiatingRequestParams,
WorkspaceKeyshareInitiatingResponse,
} from '@standardnotes/api'
import { Uuid, WorkspaceAccessLevel, WorkspaceType } from '@standardnotes/common'
@@ -26,6 +28,7 @@ import { AcceptInvitation } from '../Domain/UseCase/AcceptInvitation/AcceptInvit
import { WorkspaceUser } from '../Domain/Workspace/WorkspaceUser'
import { WorkspaceUserProjection } from '../Domain/Projection/WorkspaceUserProjection'
import { ListWorkspaceUsers } from '../Domain/UseCase/ListWorkspaceUsers/ListWorkspaceUsers'
import { InitiateKeyShare } from '../Domain/UseCase/InitiateKeyShare/InitiateKeyShare'
@injectable()
export class WorkspacesController implements WorkspaceServerInterface {
@@ -35,11 +38,41 @@ export class WorkspacesController implements WorkspaceServerInterface {
@inject(TYPES.ListWorkspaces) private doListWorkspaces: ListWorkspaces,
@inject(TYPES.ListWorkspaceUsers) private doListWorkspaceUsers: ListWorkspaceUsers,
@inject(TYPES.AcceptInvitation) private doAcceptInvite: AcceptInvitation,
@inject(TYPES.InitiateKeyShare) private doInitiateKeyshare: InitiateKeyShare,
@inject(TYPES.WorkspaceProjector) private workspaceProjector: ProjectorInterface<Workspace, WorkspaceProjection>,
@inject(TYPES.WorkspaceUserProjector)
private workspaceUserProjector: ProjectorInterface<WorkspaceUser, WorkspaceUserProjection>,
) {}
async initiateKeyshare(
params: WorkspaceKeyshareInitiatingRequestParams,
): Promise<WorkspaceKeyshareInitiatingResponse> {
const result = await this.doInitiateKeyshare.execute({
userUuid: params.userUuid,
workspaceUuid: params.workspaceUuid,
encryptedWorkspaceKey: params.encryptedWorkspaceKey,
performingUserUuid: params.performingUserUuid as Uuid,
})
if (!result.success) {
return {
status: HttpStatusCode.BadRequest,
data: {
error: {
message: 'Could not initiate keyshare.',
},
},
}
}
return {
status: HttpStatusCode.Success,
data: {
success: true,
},
}
}
async inviteToWorkspace(params: WorkspaceInvitationRequestParams): Promise<WorkspaceInvitationResponse> {
const { invite } = await this.doInviteToWorkspace.execute({
inviteeEmail: params.inviteeEmail,

View File

@@ -0,0 +1,100 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { WorkspaceUser } from '../../Workspace/WorkspaceUser'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { InitiateKeyShare } from './InitiateKeyShare'
import { WorkspaceAccessLevel } from '@standardnotes/common'
describe('InitiateKeyShare', () => {
let workspaceUserRepository: WorkspaceUserRepositoryInterface
let timer: TimerInterface
let workspaceUser: WorkspaceUser
let workspaceOwner: WorkspaceUser
const createUseCase = () => new InitiateKeyShare(workspaceUserRepository, timer)
beforeEach(() => {
workspaceOwner = {
accessLevel: WorkspaceAccessLevel.Owner,
} as jest.Mocked<WorkspaceUser>
workspaceUser = {} as jest.Mocked<WorkspaceUser>
workspaceUserRepository = {} as jest.Mocked<WorkspaceUserRepositoryInterface>
workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid = jest
.fn()
.mockReturnValueOnce(workspaceOwner)
.mockReturnValueOnce(workspaceUser)
workspaceUserRepository.save = jest.fn().mockImplementation((user: WorkspaceUser) => user)
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
})
it('should update the workspace user with a workspace key and mark as active', async () => {
await createUseCase().execute({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
performingUserUuid: 'o-1-2-3',
})
expect(workspaceUserRepository.save).toHaveBeenCalledWith({
encryptedWorkspaceKey: 'foobar',
status: 'active',
updatedAt: 1,
})
})
it('should not initiate key share if workspace is not found', async () => {
workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid = jest
.fn()
.mockReturnValueOnce(workspaceOwner)
.mockReturnValueOnce(null)
await createUseCase().execute({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
performingUserUuid: 'o-1-2-3',
})
expect(workspaceUserRepository.save).not.toHaveBeenCalled()
})
it('should not initiate key share if workspace performing user is not the owner or admin', async () => {
workspaceOwner.accessLevel = WorkspaceAccessLevel.ReadOnly
workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid = jest
.fn()
.mockReturnValueOnce(workspaceOwner)
.mockReturnValueOnce(workspaceUser)
await createUseCase().execute({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
performingUserUuid: 'o-1-2-3',
})
expect(workspaceUserRepository.save).not.toHaveBeenCalled()
})
it('should not initiate key share if workspace performing user is found in workspace', async () => {
workspaceOwner.accessLevel = WorkspaceAccessLevel.ReadOnly
workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid = jest
.fn()
.mockReturnValueOnce(null)
.mockReturnValueOnce(workspaceUser)
await createUseCase().execute({
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
encryptedWorkspaceKey: 'foobar',
performingUserUuid: 'o-1-2-3',
})
expect(workspaceUserRepository.save).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,52 @@
import { WorkspaceAccessLevel, WorkspaceUserStatus } from '@standardnotes/common'
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { WorkspaceUserRepositoryInterface } from '../../Workspace/WorkspaceUserRepositoryInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { InitiateKeyShareDTO } from './InitiateKeyShareDTO'
import { InitiateKeyShareResponse } from './InitiateKeyShareResponse'
@injectable()
export class InitiateKeyShare implements UseCaseInterface {
constructor(
@inject(TYPES.WorkspaceUserRepository) private workspaceUserRepository: WorkspaceUserRepositoryInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
) {}
async execute(dto: InitiateKeyShareDTO): Promise<InitiateKeyShareResponse> {
const workspaceOwner = await this.workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid({
workspaceUuid: dto.workspaceUuid,
userUuid: dto.performingUserUuid,
})
if (
workspaceOwner === null ||
![WorkspaceAccessLevel.Admin, WorkspaceAccessLevel.Owner].includes(workspaceOwner.accessLevel)
) {
return {
success: false,
}
}
const workspaceUser = await this.workspaceUserRepository.findOneByUserUuidAndWorkspaceUuid({
workspaceUuid: dto.workspaceUuid,
userUuid: dto.userUuid,
})
if (workspaceUser === null) {
return {
success: false,
}
}
workspaceUser.encryptedWorkspaceKey = dto.encryptedWorkspaceKey
workspaceUser.status = WorkspaceUserStatus.Active
workspaceUser.updatedAt = this.timer.getTimestampInMicroseconds()
await this.workspaceUserRepository.save(workspaceUser)
return {
success: true,
}
}
}

View File

@@ -0,0 +1,8 @@
import { Uuid } from '@standardnotes/common'
export type InitiateKeyShareDTO = {
workspaceUuid: Uuid
userUuid: Uuid
performingUserUuid: Uuid
encryptedWorkspaceKey: string
}

View File

@@ -0,0 +1,3 @@
export type InitiateKeyShareResponse = {
success: boolean
}

View File

@@ -5,4 +5,5 @@ export interface WorkspaceUserRepositoryInterface {
save(workspace: WorkspaceUser): Promise<WorkspaceUser>
findByUserUuid(userUuid: Uuid): Promise<WorkspaceUser[]>
findByWorkspaceUuid(workspaceUuid: Uuid): Promise<WorkspaceUser[]>
findOneByUserUuidAndWorkspaceUuid(dto: { workspaceUuid: Uuid; userUuid: Uuid }): Promise<WorkspaceUser | null>
}

View File

@@ -39,6 +39,18 @@ export class InversifyExpressWorkspacesController extends BaseHttpController {
return this.json(result.data, result.status)
}
@httpPost('/:workspaceUuid/users/:userUuid/keyshare')
async initiateKeyshare(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.workspacesController.initiateKeyshare({
userUuid: request.params.userUuid,
workspaceUuid: request.params.workspaceUuid,
encryptedWorkspaceKey: request.body.encryptedWorkspaceKey,
performingUserUuid: response.locals.user.uuid,
})
return this.json(result.data, result.status)
}
@httpPost('/:workspaceUuid/invites')
async inviteToWorkspace(request: Request, response: Response): Promise<results.JsonResult> {
if (request.params.workspaceUuid !== request.body.workspaceUuid) {

View File

@@ -45,4 +45,16 @@ describe('MySQLWorkspaceUserRepository', () => {
expect(queryBuilder.where).toHaveBeenCalledWith('workspace_uuid = :workspaceUuid', { workspaceUuid: 'i-1-2-3' })
})
it('should find one by workspace uuid and user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(null)
await createRepository().findOneByUserUuidAndWorkspaceUuid({ workspaceUuid: 'w-1-2-3', userUuid: 'u-1-2-3' })
expect(queryBuilder.where).toHaveBeenCalledWith('workspace_uuid = :workspaceUuid AND user_uuid = :userUuid', {
workspaceUuid: 'w-1-2-3',
userUuid: 'u-1-2-3',
})
})
})

View File

@@ -12,6 +12,19 @@ export class MySQLWorkspaceUserRepository implements WorkspaceUserRepositoryInte
private ormRepository: Repository<WorkspaceUser>,
) {}
async findOneByUserUuidAndWorkspaceUuid(dto: {
workspaceUuid: string
userUuid: string
}): Promise<WorkspaceUser | null> {
return this.ormRepository
.createQueryBuilder()
.where('workspace_uuid = :workspaceUuid AND user_uuid = :userUuid', {
workspaceUuid: dto.workspaceUuid,
userUuid: dto.userUuid,
})
.getOne()
}
async findByWorkspaceUuid(workspaceUuid: string): Promise<WorkspaceUser[]> {
return this.ormRepository.createQueryBuilder().where('workspace_uuid = :workspaceUuid', { workspaceUuid }).getMany()
}

View File

@@ -1824,18 +1824,18 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/api@npm:^1.15.0":
version: 1.15.0
resolution: "@standardnotes/api@npm:1.15.0"
"@standardnotes/api@npm:^1.16.0":
version: 1.16.0
resolution: "@standardnotes/api@npm:1.16.0"
dependencies:
"@standardnotes/common": ^1.39.0
"@standardnotes/encryption": 1.17.0
"@standardnotes/models": 1.26.0
"@standardnotes/encryption": 1.17.1
"@standardnotes/models": 1.27.0
"@standardnotes/responses": 1.11.0
"@standardnotes/security": ^1.1.0
"@standardnotes/utils": 1.10.0
reflect-metadata: ^0.1.13
checksum: 88ae0a340e175b8251e4960f377998a9e21ec5c7967be85c813ff2103d281faea8b13faf787ac51dab4d1737521df136748c9dbd34f0eee2f61d860085bca840
checksum: 465f76dd29f0207019cf16e916ab7495e0f09c5e332225a5e089bcc26b6969754b9c813f132cbc9225830705dc9d88b03fa9cbdce8f84e45ffb303be410b260f
languageName: node
linkType: hard
@@ -1846,7 +1846,7 @@ __metadata:
"@newrelic/winston-enricher": ^4.0.0
"@sentry/node": ^7.3.0
"@standardnotes/analytics": "workspace:*"
"@standardnotes/api": ^1.15.0
"@standardnotes/api": ^1.16.0
"@standardnotes/common": "workspace:*"
"@standardnotes/domain-events": "workspace:*"
"@standardnotes/domain-events-infra": "workspace:*"
@@ -1972,17 +1972,17 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/encryption@npm:1.17.0":
version: 1.17.0
resolution: "@standardnotes/encryption@npm:1.17.0"
"@standardnotes/encryption@npm:1.17.1":
version: 1.17.1
resolution: "@standardnotes/encryption@npm:1.17.1"
dependencies:
"@standardnotes/common": ^1.39.0
"@standardnotes/models": 1.26.0
"@standardnotes/models": 1.27.0
"@standardnotes/responses": 1.11.0
"@standardnotes/sncrypto-common": 1.13.0
"@standardnotes/utils": 1.10.0
reflect-metadata: ^0.1.13
checksum: 587516dfed87ba0bc46fef9ddcc6edb42757c140f852096d6bf6f7911418a7aa1eb0b370c5a0c56eeab3e6b13117ec055374b6e023c64e67cdf2362a584ac06e
checksum: 2b2408ffbd3440a89d7b2ddbaf8d50563ea126cd37c8d8ba9aa2b9cd4682f394a28fe16a5e5161685c49866def00a2cc13c3947c2b81e656f94713ac8c526a13
languageName: node
linkType: hard
@@ -2099,7 +2099,21 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/models@npm:1.26.0, @standardnotes/models@npm:^1.26.0":
"@standardnotes/models@npm:1.27.0":
version: 1.27.0
resolution: "@standardnotes/models@npm:1.27.0"
dependencies:
"@standardnotes/common": ^1.39.0
"@standardnotes/features": 1.53.0
"@standardnotes/responses": 1.11.0
"@standardnotes/utils": 1.10.0
lodash: ^4.17.21
reflect-metadata: ^0.1.13
checksum: 263fd9e9233daa997060c538cc48df8fb5598be6464fe952f79d3fb90ce759ddb8279516ecc8b56c763ee3488c2783218d289b40020835db7c51047cb21196ad
languageName: node
linkType: hard
"@standardnotes/models@npm:^1.26.0":
version: 1.26.0
resolution: "@standardnotes/models@npm:1.26.0"
dependencies:
@@ -2378,7 +2392,7 @@ __metadata:
dependencies:
"@newrelic/winston-enricher": ^4.0.0
"@sentry/node": ^7.3.0
"@standardnotes/api": ^1.15.0
"@standardnotes/api": ^1.16.0
"@standardnotes/common": "workspace:*"
"@standardnotes/domain-events": "workspace:^"
"@standardnotes/domain-events-infra": "workspace:^"