Merge branch 'main' into fabians/core-ioc-28
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-foundation divide-y divide-outline-3 mb-8 empty:mb-0">
|
||||
<div
|
||||
v-if="hasBanners"
|
||||
class="bg-foundation divide-y divide-outline-3 mb-8 empty:mb-0"
|
||||
>
|
||||
<ProjectsInviteBanners
|
||||
v-if="projectsInvites?.projectInvites?.length"
|
||||
:invites="projectsInvites"
|
||||
@@ -34,8 +37,16 @@ graphql(`
|
||||
}
|
||||
`)
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
projectsInvites?: ProjectsDashboardHeaderProjects_UserFragment
|
||||
workspacesInvites?: ProjectsDashboardHeaderWorkspaces_UserFragment
|
||||
}>()
|
||||
|
||||
const hasBanners = computed(() => {
|
||||
return (
|
||||
props.projectsInvites?.projectInvites?.length ||
|
||||
props.workspacesInvites?.workspaceInvites?.length ||
|
||||
props.workspacesInvites?.discoverableWorkspaces?.length
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -310,7 +310,7 @@ const documents = {
|
||||
"\n subscription OnViewerUserActivityBroadcasted(\n $target: ViewerUpdateTrackingTarget!\n $sessionId: String!\n ) {\n viewerUserActivityBroadcasted(target: $target, sessionId: $sessionId) {\n userName\n userId\n user {\n ...LimitedUserAvatar\n }\n state\n status\n sessionId\n }\n }\n": types.OnViewerUserActivityBroadcastedDocument,
|
||||
"\n subscription OnViewerCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n id\n type\n comment {\n id\n parent {\n id\n }\n ...ViewerCommentThread\n }\n }\n }\n": types.OnViewerCommentsUpdatedDocument,
|
||||
"\n fragment LinkableComment on Comment {\n id\n viewerResources {\n modelId\n versionId\n objectId\n }\n }\n": types.LinkableCommentFragmentDoc,
|
||||
"\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n user {\n id\n }\n }\n": types.UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n": types.UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n }\n": types.WorkspaceMixpanelUpdateGroup_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment WorkspaceMixpanelUpdateGroup_Workspace on Workspace {\n id\n name\n description\n domainBasedMembershipProtectionEnabled\n discoverabilityEnabled\n billing {\n cost {\n total\n }\n versionsCount {\n current\n max\n }\n }\n team {\n totalCount\n items {\n ...WorkspaceMixpanelUpdateGroup_WorkspaceCollaborator\n }\n }\n }\n": types.WorkspaceMixpanelUpdateGroup_WorkspaceFragmentDoc,
|
||||
"\n mutation UpdateRole($input: WorkspaceRoleUpdateInput!) {\n workspaceMutations {\n updateRole(input: $input) {\n team {\n items {\n id\n role\n }\n }\n }\n }\n }\n": types.UpdateRoleDocument,
|
||||
@@ -1541,7 +1541,7 @@ export function graphql(source: "\n fragment LinkableComment on Comment {\n
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n user {\n id\n }\n }\n"): (typeof documents)["\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n user {\n id\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n"): (typeof documents)["\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -221,6 +221,7 @@ graphql(`
|
||||
id
|
||||
token
|
||||
workspaceId
|
||||
workspaceSlug
|
||||
user {
|
||||
id
|
||||
}
|
||||
@@ -283,6 +284,7 @@ export const useWorkspaceInviteManager = <
|
||||
if (!token.value || !invite.value) return false
|
||||
|
||||
const workspaceId = invite.value.workspaceId
|
||||
const workspaceSlug = invite.value.workspaceSlug
|
||||
const shouldAddNewEmail = canAddNewEmail.value && addNewEmail
|
||||
|
||||
loading.value = true
|
||||
@@ -302,8 +304,8 @@ export const useWorkspaceInviteManager = <
|
||||
|
||||
// Redirect
|
||||
if (accept) {
|
||||
if (workspaceId) {
|
||||
window.location.href = workspaceRoute(workspaceId)
|
||||
if (workspaceSlug) {
|
||||
window.location.href = workspaceRoute(workspaceSlug)
|
||||
} else {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
@@ -381,6 +381,7 @@ type PendingWorkspaceCollaborator {
|
||||
inviteId: String!
|
||||
workspaceId: String!
|
||||
workspaceName: String!
|
||||
workspaceSlug: String!
|
||||
"""
|
||||
E-mail address if target is unregistered or primary e-mail of target registered user
|
||||
if token was specified to retrieve this invite
|
||||
|
||||
@@ -211,48 +211,56 @@ export async function addStreamCreatedActivity(params: {
|
||||
/**
|
||||
* Save "stream permissions granted to user" activity item
|
||||
*/
|
||||
export async function addStreamPermissionsAddedActivity(params: {
|
||||
streamId: string
|
||||
activityUserId: string
|
||||
targetUserId: string
|
||||
role: StreamRoles
|
||||
stream: StreamRecord
|
||||
}) {
|
||||
const { streamId, activityUserId, targetUserId, role, stream } = params
|
||||
await Promise.all([
|
||||
saveActivityFactory({ db })({
|
||||
streamId,
|
||||
resourceType: ResourceTypes.Stream,
|
||||
resourceId: streamId,
|
||||
actionType: ActionTypes.Stream.PermissionsAdd,
|
||||
userId: activityUserId,
|
||||
info: { targetUser: targetUserId, role },
|
||||
message: `Permission granted to user ${targetUserId} (${role})`
|
||||
}),
|
||||
pubsub.publish(StreamPubsubEvents.UserStreamAdded, {
|
||||
userStreamAdded: {
|
||||
id: streamId,
|
||||
sharedBy: activityUserId
|
||||
},
|
||||
ownerId: targetUserId
|
||||
}),
|
||||
publish(UserSubscriptions.UserProjectsUpdated, {
|
||||
userProjectsUpdated: {
|
||||
id: streamId,
|
||||
type: UserProjectsUpdatedMessageType.Added,
|
||||
project: stream
|
||||
},
|
||||
ownerId: targetUserId
|
||||
}),
|
||||
publish(ProjectSubscriptions.ProjectUpdated, {
|
||||
projectUpdated: {
|
||||
id: streamId,
|
||||
type: ProjectUpdatedMessageType.Updated,
|
||||
project: stream
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
export const addStreamPermissionsAddedActivityFactory =
|
||||
({
|
||||
saveActivity,
|
||||
publish
|
||||
}: {
|
||||
saveActivity: SaveActivity
|
||||
publish: PublishSubscription
|
||||
}) =>
|
||||
async (params: {
|
||||
streamId: string
|
||||
activityUserId: string
|
||||
targetUserId: string
|
||||
role: StreamRoles
|
||||
stream: StreamRecord
|
||||
}) => {
|
||||
const { streamId, activityUserId, targetUserId, role, stream } = params
|
||||
await Promise.all([
|
||||
saveActivity({
|
||||
streamId,
|
||||
resourceType: ResourceTypes.Stream,
|
||||
resourceId: streamId,
|
||||
actionType: ActionTypes.Stream.PermissionsAdd,
|
||||
userId: activityUserId,
|
||||
info: { targetUser: targetUserId, role },
|
||||
message: `Permission granted to user ${targetUserId} (${role})`
|
||||
}),
|
||||
publish(StreamPubsubEvents.UserStreamAdded, {
|
||||
userStreamAdded: {
|
||||
id: streamId,
|
||||
sharedBy: activityUserId
|
||||
},
|
||||
ownerId: targetUserId
|
||||
}),
|
||||
publish(UserSubscriptions.UserProjectsUpdated, {
|
||||
userProjectsUpdated: {
|
||||
id: streamId,
|
||||
type: UserProjectsUpdatedMessageType.Added,
|
||||
project: stream
|
||||
},
|
||||
ownerId: targetUserId
|
||||
}),
|
||||
publish(ProjectSubscriptions.ProjectUpdated, {
|
||||
projectUpdated: {
|
||||
id: streamId,
|
||||
type: ProjectUpdatedMessageType.Updated,
|
||||
project: stream
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Save "user accepted stream invite" activity item
|
||||
|
||||
@@ -1785,6 +1785,7 @@ export type PendingWorkspaceCollaborator = {
|
||||
user?: Maybe<LimitedUser>;
|
||||
workspaceId: Scalars['String']['output'];
|
||||
workspaceName: Scalars['String']['output'];
|
||||
workspaceSlug: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type PendingWorkspaceCollaboratorsFilter = {
|
||||
@@ -5478,6 +5479,7 @@ export type PendingWorkspaceCollaboratorResolvers<ContextType = GraphQLContext,
|
||||
user?: Resolver<Maybe<ResolversTypes['LimitedUser']>, ParentType, ContextType>;
|
||||
workspaceId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
workspaceName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
workspaceSlug?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ const {
|
||||
StreamAccessUpdateError
|
||||
} = require('@/modules/core/errors/stream')
|
||||
const {
|
||||
addStreamPermissionsAddedActivity,
|
||||
addStreamPermissionsRevokedActivityFactory,
|
||||
addStreamInviteAcceptedActivityFactory
|
||||
addStreamInviteAcceptedActivityFactory,
|
||||
addStreamPermissionsAddedActivityFactory
|
||||
} = require('@/modules/activitystream/services/streamActivity')
|
||||
const {
|
||||
getStream,
|
||||
@@ -200,7 +200,10 @@ async function addOrUpdateStreamCollaborator(
|
||||
stream
|
||||
})
|
||||
} else {
|
||||
await addStreamPermissionsAddedActivity({
|
||||
await addStreamPermissionsAddedActivityFactory({
|
||||
saveActivity: saveActivityFactory({ db }),
|
||||
publish
|
||||
})({
|
||||
streamId,
|
||||
activityUserId: addedById,
|
||||
targetUserId: userId,
|
||||
|
||||
@@ -1769,6 +1769,7 @@ export type PendingWorkspaceCollaborator = {
|
||||
user?: Maybe<LimitedUser>;
|
||||
workspaceId: Scalars['String']['output'];
|
||||
workspaceName: Scalars['String']['output'];
|
||||
workspaceSlug: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type PendingWorkspaceCollaboratorsFilter = {
|
||||
|
||||
@@ -46,8 +46,7 @@ describe('FileUploads @fileuploads', () => {
|
||||
;({ token: userOneToken } = await createToken({
|
||||
userId: userOneId,
|
||||
name: 'test token',
|
||||
scopes: [Scopes.Streams.Write],
|
||||
lifespan: 3600
|
||||
scopes: [Scopes.Streams.Write]
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
@@ -862,6 +862,12 @@ export = FF_WORKSPACES_MODULE_ENABLED
|
||||
)
|
||||
return workspace!.name
|
||||
},
|
||||
workspaceSlug: async (parent, _args, ctx) => {
|
||||
const workspace = await ctx.loaders.workspaces!.getWorkspace.load(
|
||||
parent.workspaceId
|
||||
)
|
||||
return workspace!.slug
|
||||
},
|
||||
invitedBy: async (parent, _args, ctx) => {
|
||||
const { invitedById } = parent
|
||||
if (!invitedById) return null
|
||||
|
||||
@@ -389,19 +389,10 @@ export const updateWorkspaceRoleFactory =
|
||||
|
||||
// Return early if no work required
|
||||
const previousWorkspaceRole = workspaceRoles.find((acl) => acl.userId === userId)
|
||||
|
||||
if (previousWorkspaceRole?.role === nextWorkspaceRole) {
|
||||
return
|
||||
}
|
||||
|
||||
// Protect against removing last admin
|
||||
if (
|
||||
isUserLastWorkspaceAdmin(workspaceRoles, userId) &&
|
||||
nextWorkspaceRole !== Roles.Workspace.Admin
|
||||
) {
|
||||
throw new WorkspaceAdminRequiredError()
|
||||
}
|
||||
|
||||
// prevent role downgrades (used during invite flow)
|
||||
if (preventRoleDowngrade) {
|
||||
if (previousWorkspaceRole) {
|
||||
@@ -416,6 +407,14 @@ export const updateWorkspaceRoleFactory =
|
||||
}
|
||||
}
|
||||
|
||||
// Protect against removing last admin
|
||||
if (
|
||||
isUserLastWorkspaceAdmin(workspaceRoles, userId) &&
|
||||
nextWorkspaceRole !== Roles.Workspace.Admin
|
||||
) {
|
||||
throw new WorkspaceAdminRequiredError()
|
||||
}
|
||||
|
||||
// ensure domain compliance
|
||||
if (nextWorkspaceRole !== Roles.Workspace.Guest) {
|
||||
const workspace = await getWorkspaceWithDomains({ id: workspaceId })
|
||||
|
||||
@@ -1770,6 +1770,7 @@ export type PendingWorkspaceCollaborator = {
|
||||
user?: Maybe<LimitedUser>;
|
||||
workspaceId: Scalars['String']['output'];
|
||||
workspaceName: Scalars['String']['output'];
|
||||
workspaceSlug: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type PendingWorkspaceCollaboratorsFilter = {
|
||||
|
||||
Reference in New Issue
Block a user