feat(workspaces): project creation emit domain event
* feat(workspaces): drop createdByUserId from the dataschema * feat(workspaces): repositories WIP * merge * protect against removing last admin in workspace * quick impl and stub tests * add tests * services * unit tests for role services * feat(workspaces): authorize project creation if workspace specified * feat(workspaces): emit project created event * fix(workspaces): protect against adding a project to a workspace if module not enabled * fix(workspaces): oops broke tests during merge --------- Co-authored-by: Gergő Jedlicska <gergo@jedlicska.com>
This commit is contained in:
@@ -30,6 +30,7 @@ input ProjectCreateInput {
|
||||
name: String
|
||||
description: String
|
||||
visibility: ProjectVisibility
|
||||
workspaceId: String
|
||||
}
|
||||
|
||||
input ProjectUpdateRoleInput {
|
||||
|
||||
@@ -316,6 +316,7 @@ input StreamCreateInput {
|
||||
Optionally specify user IDs of users that you want to invite to be contributors to this stream
|
||||
"""
|
||||
withContributors: [String!]
|
||||
workspaceId: String
|
||||
}
|
||||
|
||||
input StreamUpdateInput {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { BaseError } from '@/modules/shared/errors/base'
|
||||
|
||||
export class WorkspacesModuleDisabledError extends BaseError {
|
||||
static defaultMessage = 'Workspaces are not enabled on this server'
|
||||
static code = 'WORKSPACES_MODULE_DISABLED_ERROR'
|
||||
static statusCode = 403
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { StreamRecord } from '@/modules/core/helpers/types'
|
||||
import { initializeModuleEventEmitter } from '@/modules/shared/services/moduleEventEmitterSetup'
|
||||
|
||||
export const ProjectEvents = {
|
||||
Created: 'created'
|
||||
} as const
|
||||
|
||||
export type ProjectEvents = (typeof ProjectEvents)[keyof typeof ProjectEvents]
|
||||
|
||||
export type ProjectEventsPayloads = {
|
||||
[ProjectEvents.Created]: { project: StreamRecord }
|
||||
}
|
||||
|
||||
const { emit, listen } = initializeModuleEventEmitter<ProjectEventsPayloads>({
|
||||
moduleName: 'core',
|
||||
namespace: 'projects'
|
||||
})
|
||||
|
||||
export const ProjectsEmitter = { emit, listen, events: ProjectEvents }
|
||||
@@ -2015,6 +2015,7 @@ export type ProjectCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
visibility?: InputMaybe<ProjectVisibility>;
|
||||
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type ProjectFileImportUpdatedMessage = {
|
||||
@@ -2886,6 +2887,7 @@ export type StreamCreateInput = {
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Optionally specify user IDs of users that you want to invite to be contributors to this stream */
|
||||
withContributors?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type StreamInviteCreateInput = {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import db from '@/db/knex'
|
||||
import { RateLimitError } from '@/modules/core/errors/ratelimit'
|
||||
import { StreamNotFoundError } from '@/modules/core/errors/stream'
|
||||
import { WorkspacesModuleDisabledError } from '@/modules/core/errors/workspaces'
|
||||
import {
|
||||
ProjectVisibility,
|
||||
Resolvers,
|
||||
TokenResourceIdentifierType
|
||||
} from '@/modules/core/graph/generated/graphql'
|
||||
import { isWorkspacesModuleEnabled } from '@/modules/core/helpers/features'
|
||||
import { Roles, Scopes, StreamRoles } from '@/modules/core/helpers/mainConstants'
|
||||
import { isResourceAllowed, toProjectIdWhitelist } from '@/modules/core/helpers/token'
|
||||
import {
|
||||
@@ -114,6 +116,19 @@ export = {
|
||||
throw new RateLimitError(rateLimitResult)
|
||||
}
|
||||
|
||||
if (!!args.input?.workspaceId) {
|
||||
if (!isWorkspacesModuleEnabled()) {
|
||||
// Ugly but complete, will go away if/when resolver moved to workspaces module
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
}
|
||||
await authorizeResolver(
|
||||
context.userId!,
|
||||
args.input.workspaceId,
|
||||
Roles.Workspace.Member,
|
||||
context.resourceAccessRules
|
||||
)
|
||||
}
|
||||
|
||||
const project = await createStreamReturnRecord(
|
||||
{
|
||||
...(args.input || {}),
|
||||
|
||||
@@ -61,6 +61,8 @@ const {
|
||||
queryAllStreamInvitesFactory
|
||||
} = require('@/modules/serverinvites/repositories/serverInvites')
|
||||
const db = require('@/db/knex')
|
||||
const { isWorkspacesModuleEnabled } = require('@/modules/core/helpers/features')
|
||||
const { WorkspacesModuleDisabledError } = require('@/modules/core/errors/workspaces')
|
||||
|
||||
// subscription events
|
||||
const USER_STREAM_ADDED = StreamPubsubEvents.UserStreamAdded
|
||||
@@ -259,6 +261,19 @@ module.exports = {
|
||||
throw new RateLimitError(rateLimitResult)
|
||||
}
|
||||
|
||||
if (args.stream.workspaceId) {
|
||||
if (!isWorkspacesModuleEnabled()) {
|
||||
// Ugly but complete, will go away if/when resolver moved to workspaces module
|
||||
throw new WorkspacesModuleDisabledError()
|
||||
}
|
||||
await authorizeResolver(
|
||||
context.userId,
|
||||
args.stream.workspaceId,
|
||||
Roles.Workspace.Member,
|
||||
context.resourceAccessRules
|
||||
)
|
||||
}
|
||||
|
||||
const { id } = await createStreamReturnRecord(
|
||||
{
|
||||
...args.stream,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { getFeatureFlags } from '@/modules/shared/helpers/envHelper'
|
||||
|
||||
export const isWorkspacesModuleEnabled = (): boolean => {
|
||||
return getFeatureFlags().FF_WORKSPACES_MODULE_ENABLED
|
||||
}
|
||||
@@ -761,7 +761,7 @@ export async function createStream(
|
||||
trx: Knex.Transaction
|
||||
}>
|
||||
) {
|
||||
const { name, description } = input
|
||||
const { name, description, workspaceId } = input
|
||||
const { ownerId, trx } = options || {}
|
||||
|
||||
let shouldBePublic: boolean, shouldBeDiscoverable: boolean
|
||||
@@ -782,7 +782,8 @@ export async function createStream(
|
||||
description: description || '',
|
||||
isPublic: shouldBePublic,
|
||||
isDiscoverable: shouldBeDiscoverable,
|
||||
updatedAt: knex.fn.now()
|
||||
updatedAt: knex.fn.now(),
|
||||
workspaceId: workspaceId || null
|
||||
}
|
||||
|
||||
// Create the stream & set up permissions
|
||||
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
TokenResourceIdentifier,
|
||||
TokenResourceIdentifierType
|
||||
} from '@/modules/core/domain/tokens/types'
|
||||
import { ProjectEvents, ProjectsEmitter } from '@/modules/core/events/projectsEmitter'
|
||||
|
||||
export async function createStreamReturnRecord(
|
||||
params: (StreamCreateInput | ProjectCreateInput) & {
|
||||
@@ -106,6 +107,8 @@ export async function createStreamReturnRecord(
|
||||
})
|
||||
}
|
||||
|
||||
await ProjectsEmitter.emit(ProjectEvents.Created, { project: stream })
|
||||
|
||||
return stream
|
||||
}
|
||||
|
||||
|
||||
@@ -2005,6 +2005,7 @@ export type ProjectCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
visibility?: InputMaybe<ProjectVisibility>;
|
||||
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type ProjectFileImportUpdatedMessage = {
|
||||
@@ -2876,6 +2877,7 @@ export type StreamCreateInput = {
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Optionally specify user IDs of users that you want to invite to be contributors to this stream */
|
||||
withContributors?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type StreamInviteCreateInput = {
|
||||
|
||||
@@ -2006,6 +2006,7 @@ export type ProjectCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
visibility?: InputMaybe<ProjectVisibility>;
|
||||
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type ProjectFileImportUpdatedMessage = {
|
||||
@@ -2877,6 +2878,7 @@ export type StreamCreateInput = {
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Optionally specify user IDs of users that you want to invite to be contributors to this stream */
|
||||
withContributors?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type StreamInviteCreateInput = {
|
||||
|
||||
Reference in New Issue
Block a user