Files
speckle-server/packages/server/modules/serverinvites/services/inviteProcessingService.ts
T
Gergő Jedlicska c6cd4c311d feat(serverinvites): create domain module in server invites (#2401)
* 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>
2024-06-25 13:24:37 +02:00

213 lines
5.7 KiB
TypeScript

import { Roles } from '@/modules/core/helpers/mainConstants'
import { getStreamRoute } from '@/modules/core/helpers/routeHelper'
import { NoInviteFoundError } from '@/modules/serverinvites/errors'
import {
isStreamInvite,
buildUserTarget,
ResourceTargets
} from '@/modules/serverinvites/helpers/inviteHelper'
import { addOrUpdateStreamCollaborator } from '@/modules/core/services/streams/streamAccessService'
import { addStreamInviteDeclinedActivity } from '@/modules/activitystream/services/streamActivity'
import { getFrontendOrigin } from '@/modules/shared/helpers/envHelper'
import { ServerInviteRecord } from '@/modules/serverinvites/domain/types'
import {
DeleteInvite,
DeleteInvitesByTarget,
DeleteServerOnlyInvites,
DeleteStreamInvite,
FindInvite,
FindServerInvite,
FindStreamInvite,
UpdateAllInviteTargets
} from '@/modules/serverinvites/domain/operations'
import {
FinalizeStreamInvite,
ResendInviteEmail
} from '@/modules/serverinvites/services/operations'
/**
* Resolve the relative auth redirect path, after registering with an invite
* Note: Important auth query string params like the access_code are added separately
* in auth middlewares
*/
export const resolveAuthRedirectPath = () => (invite?: ServerInviteRecord) => {
if (invite) {
const { resourceId } = invite
if (isStreamInvite(invite)) {
// TODO: check nullability
return `${getStreamRoute(resourceId!)}`
}
}
// Fall-back to base URL (for server invites)
return getFrontendOrigin()
}
/**
* Validate that the new user has a valid invite for registering to the server
*/
export const validateServerInvite =
({ findServerInvite }: { findServerInvite: FindServerInvite }) =>
async (email: string, token: string): Promise<ServerInviteRecord> => {
const invite = await findServerInvite(email, token)
if (!invite) {
throw new NoInviteFoundError(
token
? "Wrong e-mail address or invite token. Make sure you're using the same e-mail address that received the invite."
: "Wrong e-mail address. Make sure you're using the same e-mail address that received the invite.",
{
info: {
email,
token
}
}
)
}
return invite
}
/**
* Finalize server registration by deleting unnecessary invites and updating
* the remaining ones
*/
export const finalizeInvitedServerRegistration =
({
deleteServerOnlyInvites,
updateAllInviteTargets
}: {
deleteServerOnlyInvites: DeleteServerOnlyInvites
updateAllInviteTargets: UpdateAllInviteTargets
}) =>
async (email: string, userId: string) => {
// Delete all server-only invites for this email
await deleteServerOnlyInvites(email)
// Update all remaining invites to use a userId target, not the e-mail
// (in case the user changes his e-mail right after)
await updateAllInviteTargets(email, buildUserTarget(userId)!)
}
/**
* Accept or decline a stream invite
*/
export const finalizeStreamInvite =
({
findStreamInvite,
deleteInvitesByTarget
}: {
findStreamInvite: FindStreamInvite
deleteInvitesByTarget: DeleteInvitesByTarget
}): FinalizeStreamInvite =>
async (accept, streamId, token, userId) => {
const invite = await findStreamInvite(streamId, {
token,
target: buildUserTarget(userId)
})
if (!invite) {
throw new NoInviteFoundError('Attempted to finalize nonexistant stream invite', {
info: {
streamId,
token,
userId
}
})
}
// Invite found - accept or decline
if (accept) {
// Add access for user
const { role = Roles.Stream.Contributor, inviterId } = invite
// TODO: check role nullability
await addOrUpdateStreamCollaborator(streamId, userId, role!, inviterId, null, {
fromInvite: true
})
// Delete all invites to this stream
await deleteInvitesByTarget(
buildUserTarget(userId)!,
ResourceTargets.Streams,
streamId
)
} else {
await addStreamInviteDeclinedActivity({
streamId,
inviteTargetId: userId,
inviterId: invite.inviterId
})
}
// Delete all invites to this stream
await deleteInvitesByTarget(
buildUserTarget(userId)!,
ResourceTargets.Streams,
streamId
)
}
/**
* Cancel/decline a stream invite
*/
export const cancelStreamInvite =
({
findStreamInvite,
deleteStreamInvite
}: {
findStreamInvite: FindStreamInvite
deleteStreamInvite: DeleteStreamInvite
}) =>
async (streamId: string, inviteId: string) => {
const invite = await findStreamInvite(streamId, {
inviteId
})
if (!invite) {
throw new NoInviteFoundError('Attempted to process nonexistant stream invite', {
info: {
streamId,
inviteId
}
})
}
await deleteStreamInvite(invite.id)
}
/**
* Re-send pending invite e-mail, without creating a new invite
*/
export const resendInvite =
({
findInvite,
resendInviteEmail
}: {
resendInviteEmail: ResendInviteEmail
findInvite: FindInvite
}) =>
async (inviteId: string) => {
const invite = await findInvite(inviteId)
if (!invite) {
throw new NoInviteFoundError('Attempted to re-send a nonexistant invite')
}
await resendInviteEmail(invite)
}
/**
* Delete pending invite
*/
export const deleteInvite =
({
findInvite,
deleteInvite
}: {
findInvite: FindInvite
deleteInvite: DeleteInvite
}) =>
async (inviteId: string) => {
const invite = await findInvite(inviteId)
if (!invite) {
throw new NoInviteFoundError('Attempted to delete a nonexistant invite')
}
await deleteInvite(invite.id)
}