diff --git a/packages/core/src/libraries/organization-invitation.ts b/packages/core/src/libraries/organization-invitation.ts index ad27fb2dfa05..17859be08bd1 100644 --- a/packages/core/src/libraries/organization-invitation.ts +++ b/packages/core/src/libraries/organization-invitation.ts @@ -66,6 +66,11 @@ export class OrganizationInvitationLibrary { return this.queries.pool.transaction(async (connection) => { const organizationQueries = new OrganizationQueries(connection); + // Check if any pending invitation has expired, if yes, update the invitation status to "Expired" first + // Note: Even if the status may appear to be "Expired", the actual data in DB may still be "Pending". + // Check `findEntities` in `OrganizationQueries` for more details. + await organizationQueries.invitations.updateExpiredEntities({ invitee, organizationId }); + // Insert the new invitation const invitation = await organizationQueries.invitations.insert({ id: generateStandardId(), inviterId, diff --git a/packages/core/src/queries/organization/index.ts b/packages/core/src/queries/organization/index.ts index 9a44a065a79b..b3e7303d10d9 100644 --- a/packages/core/src/queries/organization/index.ts +++ b/packages/core/src/queries/organization/index.ts @@ -116,6 +116,33 @@ class OrganizationInvitationsQueries extends SchemaQueries< return this.pool.any(this.#findEntity({ ...options, invitationId: undefined })); } + async updateExpiredEntities({ + invitationId, + organizationId, + inviterId, + invitee, + }: OrganizationInvitationSearchOptions): Promise { + const { table, fields } = convertToIdentifiers(OrganizationInvitations); + await this.pool.query(sql` + update ${table} + set ${fields.status} = ${OrganizationInvitationStatus.Expired} + where ${fields.status} = ${OrganizationInvitationStatus.Pending} + and ${fields.expiresAt} < now() + ${conditionalSql(invitationId, (id) => { + return sql`and ${fields.id} = ${id}`; + })} + ${conditionalSql(organizationId, (id) => { + return sql`and ${fields.organizationId} = ${id}`; + })} + ${conditionalSql(inviterId, (id) => { + return sql`and ${fields.inviterId} = ${id}`; + })} + ${conditionalSql(invitee, (email) => { + return sql`and ${fields.invitee} = ${email}`; + })} + `); + } + #findEntity({ invitationId, organizationId,