Files
speckle-server/packages/server/modules/serverinvites/services/coreEmailContents.ts
T
Kristaps Fabians Geikins ede566eed9 feat(server): serverInvites refactor + workspace invites CRUD & GQL API (#2530)
* prep for new resources algo

* typescriptifying stuff

* minor types fix

* migrate to resources col

* repo & creation updated, WIP processing/retrieval

* WIP invite processing

* finished finalization refactor

* project invite management

* transformed all invites services

* fixed up projects & core serverinvites resolvers

* test fixes

* WIP workspace create GQL & test

* basic invite creation test works

* a buncha working tests

* more tests

* cancelation tests

* minor invite use refactor

* invite retrieval tasks

* invite use() works as expected

* filtering out broken invites

* enabled invite retrieval by token irregardless of who is it for

* minor adjustments

* tests fix

* test config improvements

* test env adjustment

* extra test case

* making resource access limits harder to ignore

* linter fixes

* eventBus type cleanup

* better generic names

* refactored serverinvites resource migration

* fix(server): better error message in project invite edge case
2024-07-29 14:37:54 +03:00

206 lines
5.8 KiB
TypeScript

import {
getRegistrationRoute,
getStreamRoute
} from '@/modules/core/helpers/routeHelper'
import { StreamRecord } from '@/modules/core/helpers/types'
import { getStream } from '@/modules/core/repositories/streams'
import {
EmailTemplateParams,
sanitizeMessage
} from '@/modules/emails/services/emailRendering'
import { ProjectInviteResourceTarget } from '@/modules/serverinvites/domain/types'
import { InviteCreateValidationError } from '@/modules/serverinvites/errors'
import {
isProjectResourceTarget,
isServerResourceTarget
} from '@/modules/serverinvites/helpers/core'
import { BuildInviteEmailContents } from '@/modules/serverinvites/services/operations'
import { getFrontendOrigin } from '@/modules/shared/helpers/envHelper'
function buildServerMjmlPreamble(params: Parameters<BuildInviteEmailContents>[0]) {
const { invite, serverInfo, inviter } = params
const { message } = invite
const bodyStart = `
<mj-text>
Hello!
<br />
<br />
${inviter.name} has just sent you this invitation to join the <b>${
serverInfo.name
}</b> Speckle Server!
${message ? inviter.name + ' said: <em>"' + message + '"</em>' : ''}
</mj-text>
`
return {
bodyStart,
bodyEnd:
'<mj-text>Feel free to ignore this invite if you do not know the person sending it.</mj-text>'
}
}
function buildServerTextPreamble(params: Parameters<BuildInviteEmailContents>[0]) {
const { invite, serverInfo, inviter } = params
const { message } = invite
const bodyStart = `Hello!
${inviter.name} has just sent you this invitation to join the ${
serverInfo.name
} Speckle Server!
${message ? inviter.name + ' said: "' + sanitizeMessage(message, true) + '"' : ''}`
return {
bodyStart,
bodyEnd: 'Feel free to ignore this invite if you do not know the person sending it.'
}
}
const buildServerEmailTemplateParams = (
params: Parameters<BuildInviteEmailContents>[0]
): EmailTemplateParams => {
const { invite } = params
const inviteLink = new URL(
`${getRegistrationRoute()}?token=${invite.token}`,
getFrontendOrigin()
).toString()
return {
mjml: buildServerMjmlPreamble(params),
text: buildServerTextPreamble(params),
cta: {
title: 'Accept the invitation',
url: inviteLink
}
}
}
const buildServerInviteContentsFactory = (): BuildInviteEmailContents => (params) => {
const { inviter } = params
const subject = 'Speckle Invitation from ' + inviter.name
return {
emailParams: buildServerEmailTemplateParams(params),
subject
}
}
type BuildProjectInviteEmailContents = (
params: Parameters<BuildInviteEmailContents>[0] & {
primaryResourceTarget: ProjectInviteResourceTarget
}
) => ReturnType<BuildInviteEmailContents>
function buildProjectMjmlPreamble(
params: Parameters<BuildProjectInviteEmailContents>[0] & { project: StreamRecord }
) {
const { invite, inviter, project } = params
const { message } = invite
const bodyStart = `
<mj-text>
Hello!
<br />
<br />
${
inviter.name
} has just sent you this invitation to become a collaborator on the <b>${
project.name
}</b> project!
${message ? inviter.name + ' said: <em>"' + message + '"</em>' : ''}
</mj-text>
`
return {
bodyStart,
bodyEnd:
'<mj-text>Feel free to ignore this invite if you do not know the person sending it.</mj-text>'
}
}
function buildProjectTextPreamble(
params: Parameters<BuildProjectInviteEmailContents>[0] & { project: StreamRecord }
) {
const { invite, project, inviter } = params
const { message } = invite
const bodyStart = `Hello!
${inviter.name} has just sent you this invitation to become a collaborator on the "${
project.name
}" project!
${message ? inviter.name + ' said: "' + sanitizeMessage(message, true) + '"' : ''}`
return {
bodyStart,
bodyEnd: 'Feel free to ignore this invite if you do not know the person sending it.'
}
}
const buildProjectEmailTemplateParams = (
params: Parameters<BuildProjectInviteEmailContents>[0] & { project: StreamRecord }
): EmailTemplateParams => {
const { invite, project } = params
const inviteLink = new URL(
`${getStreamRoute(project.id)}?token=${invite.token}&accept=true`,
getFrontendOrigin()
).toString()
return {
mjml: buildProjectMjmlPreamble(params),
text: buildProjectTextPreamble(params),
cta: {
title: 'Accept the invitation',
url: inviteLink
}
}
}
type BuildProjectInviteContentsFactoryDeps = { getStream: typeof getStream }
const buildProjectInviteContentsFactory =
(deps: BuildProjectInviteContentsFactoryDeps): BuildProjectInviteEmailContents =>
async (params) => {
const { getStream } = deps
const { inviter, primaryResourceTarget } = params
const project = await getStream({ streamId: primaryResourceTarget.resourceId })
if (!project) {
throw new InviteCreateValidationError(
'Attempting to invite into a non-existant project'
)
}
const subject = `${inviter.name} wants to share the project "${project.name}" on Speckle with you`
return {
emailParams: buildProjectEmailTemplateParams({ ...params, project }),
subject
}
}
export type BuildInviteContentsFactoryDeps = BuildProjectInviteContentsFactoryDeps
export const buildCoreInviteEmailContentsFactory =
(deps: BuildProjectInviteContentsFactoryDeps): BuildInviteEmailContents =>
(params) => {
const { invite } = params
const primaryResourceTarget = invite.resource
if (isServerResourceTarget(primaryResourceTarget)) {
return buildServerInviteContentsFactory()(params)
} else if (isProjectResourceTarget(primaryResourceTarget)) {
return buildProjectInviteContentsFactory(deps)({
...params,
primaryResourceTarget
})
} else {
throw new InviteCreateValidationError(
'Unexpected resource target type: ' + primaryResourceTarget.resourceType
)
}
}