c6cd4c311d
* chore(serverinvites): repository refactor for multiregion * chore(serverinvites): remove migrated functions from old repository * chore(serverinvites): refactor serverInviteForToken resolver for multiregion * chore(serverinvites): invite processing service refactor for multiregion * chore(serverinvites): subscription refactor for multiregion * chore(serverinvites): move buildEmailContents to dedicated file * chore(serverinvites): deleteAllStreamInvites function multiregion refactor * chore(serverinvites): refactor deleteServerOnlyInvites multiregion repository * chore(serverinvites): complete repository refactor for multiregion * feat(serverinvites): create domain module in server invites * fix(serverinvites): no relative imports * feat(serverinvites): extract individual types from repository * feat(serverinvites): move interfaces to operations * fix(serverinvites): update imports referencing old interfaces file * fix(serverinvites): type mismatch for insert invite and delete old * chore(serverinvites): refactor to single repo function * test(serverinvites): fix tests * fix(serverinvites): use domain types in all places * feat(serverinvites): WIP unity * feat(serverinvites): move to new facory names and types * feat(serverinvites): fix tests * fix(serverinvites): use factory name --------- Co-authored-by: Alessandro Magionami <alessandro.magionami@gmail.com>
156 lines
4.9 KiB
TypeScript
156 lines
4.9 KiB
TypeScript
import { ServerInviteGraphQLReturnType } from '@/modules/core/helpers/graphTypes'
|
|
import { LimitedUserRecord } from '@/modules/core/helpers/types'
|
|
import { removePrivateFields } from '@/modules/core/helpers/userHelper'
|
|
import { getUsers, getUser } from '@/modules/core/repositories/users'
|
|
import { NoInviteFoundError } from '@/modules/serverinvites/errors'
|
|
import { PendingStreamCollaboratorGraphQLReturn } from '@/modules/serverinvites/helpers/graphTypes'
|
|
import {
|
|
resolveTarget,
|
|
resolveInviteTargetTitle,
|
|
buildUserTarget
|
|
} from '@/modules/serverinvites/helpers/inviteHelper'
|
|
import {
|
|
ServerInviteRecord,
|
|
StreamInviteRecord
|
|
} from '@/modules/serverinvites/domain/types'
|
|
import { MaybeNullOrUndefined, Nullable, Roles } from '@speckle/shared'
|
|
import { keyBy, uniq } from 'lodash'
|
|
import {
|
|
FindServerInvite,
|
|
FindStreamInvite,
|
|
QueryAllStreamInvites,
|
|
QueryAllUserStreamInvites
|
|
} from '@/modules/serverinvites/domain/operations'
|
|
|
|
/**
|
|
* The token field is intentionally ommited from this and only managed through the .token resolver
|
|
* for extra security - so that no one accidentally returns it out from this service
|
|
*/
|
|
type PendingStreamCollaboratorGraphQLType = PendingStreamCollaboratorGraphQLReturn
|
|
|
|
function buildPendingStreamCollaboratorId(invite: ServerInviteRecord) {
|
|
return `invite:${invite.id}`
|
|
}
|
|
|
|
function buildPendingStreamCollaboratorModel(
|
|
invite: StreamInviteRecord,
|
|
targetUser: Nullable<LimitedUserRecord>
|
|
): PendingStreamCollaboratorGraphQLType {
|
|
const { resourceId } = invite
|
|
|
|
return {
|
|
id: buildPendingStreamCollaboratorId(invite),
|
|
inviteId: invite.id,
|
|
streamId: resourceId,
|
|
title: resolveInviteTargetTitle(invite, targetUser),
|
|
role: invite.role || Roles.Stream.Contributor,
|
|
invitedById: invite.inviterId,
|
|
user: targetUser
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all registered invitation target users keyed by their ID
|
|
*/
|
|
async function getInvitationTargetUsers(invites: ServerInviteRecord[]) {
|
|
const userIds = uniq(
|
|
invites
|
|
.map((i) => resolveTarget(i.target).userId)
|
|
.filter((id): id is NonNullable<typeof id> => !!id)
|
|
)
|
|
if (!userIds.length) return {}
|
|
|
|
const users = await getUsers(userIds)
|
|
return keyBy(users, 'id')
|
|
}
|
|
|
|
/**
|
|
* Get pending stream collaborators (invited, but not accepted)
|
|
*/
|
|
export const getPendingStreamCollaborators =
|
|
({ queryAllStreamInvites }: { queryAllStreamInvites: QueryAllStreamInvites }) =>
|
|
async (streamId: string): Promise<PendingStreamCollaboratorGraphQLType[]> => {
|
|
// Get all pending invites
|
|
const invites = await queryAllStreamInvites(streamId)
|
|
|
|
// Get all target users, if any
|
|
const usersById = await getInvitationTargetUsers(invites)
|
|
|
|
// Build results
|
|
const results = []
|
|
for (const invite of invites) {
|
|
let user: LimitedUserRecord | null = null
|
|
const { userId } = resolveTarget(invite.target)
|
|
if (userId && usersById[userId]) {
|
|
user = removePrivateFields(usersById[userId])
|
|
}
|
|
|
|
results.push(buildPendingStreamCollaboratorModel(invite, user))
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
/**
|
|
* Find a pending invitation to the specified stream for the specified user
|
|
* Either the user ID or invite ID must be set
|
|
*/
|
|
export const getUserPendingStreamInvite =
|
|
({ findStreamInvite }: { findStreamInvite: FindStreamInvite }) =>
|
|
async (
|
|
streamId: string,
|
|
userId: MaybeNullOrUndefined<string>,
|
|
token: MaybeNullOrUndefined<string>
|
|
): Promise<Nullable<PendingStreamCollaboratorGraphQLType>> => {
|
|
if (!userId && !token) return null
|
|
|
|
const invite = await findStreamInvite(streamId, {
|
|
target: buildUserTarget(userId),
|
|
token
|
|
})
|
|
if (!invite) return null
|
|
|
|
// TODO: user repo should be injected
|
|
const targetUser = userId ? await getUser(userId) : null
|
|
|
|
return buildPendingStreamCollaboratorModel(invite, targetUser)
|
|
}
|
|
|
|
/**
|
|
* Get all pending invitations to streams that this user has
|
|
*/
|
|
export const getUserPendingStreamInvites =
|
|
({
|
|
queryAllUserStreamInvites
|
|
}: {
|
|
queryAllUserStreamInvites: QueryAllUserStreamInvites
|
|
}) =>
|
|
async (userId: string): Promise<PendingStreamCollaboratorGraphQLType[]> => {
|
|
if (!userId) return []
|
|
|
|
// TODO: user repository should be injected
|
|
const targetUser = await getUser(userId)
|
|
if (!targetUser) {
|
|
throw new NoInviteFoundError('Nonexistant user specified')
|
|
}
|
|
|
|
const invites = await queryAllUserStreamInvites(userId)
|
|
return invites.map((i) => buildPendingStreamCollaboratorModel(i, targetUser))
|
|
}
|
|
|
|
export const getServerInviteForToken =
|
|
({ findServerInvite }: { findServerInvite: FindServerInvite }) =>
|
|
async (token: string): Promise<Nullable<ServerInviteGraphQLReturnType>> => {
|
|
const invite = await findServerInvite(undefined, token)
|
|
if (!invite) return null
|
|
|
|
const target = resolveTarget(invite.target)
|
|
if (!target.userEmail) return null
|
|
|
|
return {
|
|
id: invite.id,
|
|
invitedById: invite.inviterId,
|
|
email: target.userEmail
|
|
}
|
|
}
|