Merge pull request #4761 from specklesystems/iain/duplicate-workspace-join-request

fix(server/workspaces): gracefully handle duplicate workspace join requests
This commit is contained in:
Iain Sproat
2025-05-19 12:22:24 +01:00
committed by GitHub
3 changed files with 100 additions and 2 deletions
@@ -23,7 +23,11 @@ const tables = {
export const createWorkspaceJoinRequestFactory =
({ db }: { db: Knex }): CreateWorkspaceJoinRequest =>
async ({ workspaceJoinRequest }) => {
const res = await tables.workspaceJoinRequests(db).insert(workspaceJoinRequest, '*')
const res = await tables
.workspaceJoinRequests(db)
.insert(workspaceJoinRequest, '*')
.onConflict()
.ignore()
return res[0]
}
@@ -84,7 +84,7 @@ export const requestToJoinWorkspaceFactory =
throw new WorkspaceProtectedError()
}
await createWorkspaceJoinRequest({
const joinRequest = await createWorkspaceJoinRequest({
workspaceJoinRequest: {
userId,
workspaceId,
@@ -92,6 +92,11 @@ export const requestToJoinWorkspaceFactory =
}
})
if (!joinRequest || joinRequest.status !== 'pending') {
// The request was already created, so don't send the email again
return true
}
await sendWorkspaceJoinRequestReceivedEmail({
workspace,
requester
@@ -233,6 +233,95 @@ const { FF_WORKSPACES_MODULE_ENABLED } = getFeatureFlags()
)
expect(sendWorkspaceJoinRequestReceivedEmailCalls[0].requester).to.equal(user)
})
it('duplicate request is idempotent', async () => {
const createWorkspaceJoinRequest = createWorkspaceJoinRequestFactory({ db })
const sendWorkspaceJoinRequestReceivedEmailCalls: Parameters<SendWorkspaceJoinRequestReceivedEmail>[number][] =
[]
const sendWorkspaceJoinRequestReceivedEmail = async (
args: Parameters<SendWorkspaceJoinRequestReceivedEmail>[number]
) => sendWorkspaceJoinRequestReceivedEmailCalls.push(args)
const user: BasicTestUser = {
id: '',
name: 'John Speckle',
email: `${createRandomString()}@example.org`,
role: Roles.Server.Admin,
verified: true
}
await createTestUser(user)
const workspace: BasicTestWorkspace = {
id: '',
slug: '',
ownerId: '',
name: cryptoRandomString({ length: 6 }),
description: cryptoRandomString({ length: 12 }),
discoverabilityEnabled: true
}
await createTestWorkspace(workspace, user, { domain: 'example.org' })
const domain = {
id: cryptoRandomString({ length: 10 }),
workspaceId: workspace.id,
domain: 'example.org',
verified: true,
createdAt: new Date(),
createdByUserId: user.id,
updatedAt: new Date()
}
const requestToJoinWorkspace = await requestToJoinWorkspaceFactory({
createWorkspaceJoinRequest,
sendWorkspaceJoinRequestReceivedEmail:
sendWorkspaceJoinRequestReceivedEmail as unknown as SendWorkspaceJoinRequestReceivedEmail,
getUserById: async () => user as unknown as UserWithOptionalRole,
getWorkspaceWithDomains: async () =>
({
...workspace,
domains: [domain]
} as unknown as WorkspaceWithDomains),
getUserEmails: async () =>
[{ email: user.email, verified: true }] as unknown as UserEmail[]
})
expect(
await requestToJoinWorkspace({ workspaceId: workspace.id, userId: user.id })
).to.equal(true)
expect(
(await db<WorkspaceJoinRequest>(WorkspaceJoinRequests.name)
.where({
workspaceId: workspace.id,
userId: user.id
})
.select('status')
.first())!.status
).to.equal('pending')
expect(sendWorkspaceJoinRequestReceivedEmailCalls).to.have.length(1)
expect(sendWorkspaceJoinRequestReceivedEmailCalls[0].workspace.id).to.equal(
workspace.id
)
expect(sendWorkspaceJoinRequestReceivedEmailCalls[0].requester).to.equal(user)
// attempt to join again
expect(
await requestToJoinWorkspace({ workspaceId: workspace.id, userId: user.id })
).to.equal(true)
expect(
(await db<WorkspaceJoinRequest>(WorkspaceJoinRequests.name)
.where({
workspaceId: workspace.id,
userId: user.id
})
.select('status')
.first())!.status
).to.equal('pending')
expect(sendWorkspaceJoinRequestReceivedEmailCalls).to.have.length(1)
})
})
describe('approveWorkspaceJoinRequestFactory, returns a function that ', () => {