From 9137fa4ca5719bb41f6b2766a936f561f72635f2 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 7 Jul 2025 13:31:20 -0400 Subject: [PATCH 1/2] Improve + fix invitation reminder logic --- .../20250707162850_last-invited-at-default.ts | 21 +++++++++++++++++++ .../org-membership/org-membership-dal.ts | 19 ++++++++++++++--- backend/src/services/org/org-service.ts | 17 ++++++++++----- 3 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 backend/src/db/migrations/20250707162850_last-invited-at-default.ts diff --git a/backend/src/db/migrations/20250707162850_last-invited-at-default.ts b/backend/src/db/migrations/20250707162850_last-invited-at-default.ts new file mode 100644 index 000000000..bfb7b85d6 --- /dev/null +++ b/backend/src/db/migrations/20250707162850_last-invited-at-default.ts @@ -0,0 +1,21 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const hasColumn = await knex.schema.hasColumn(TableName.OrgMembership, "lastInvitedAt"); + if (hasColumn) { + await knex.schema.alterTable(TableName.OrgMembership, (t) => { + t.datetime("lastInvitedAt").nullable().defaultTo(knex.fn.now()).alter(); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasColumn = await knex.schema.hasColumn(TableName.OrgMembership, "lastInvitedAt"); + if (hasColumn) { + await knex.schema.alterTable(TableName.OrgMembership, (t) => { + t.datetime("lastInvitedAt").nullable().alter(); + }); + } +} diff --git a/backend/src/services/org-membership/org-membership-dal.ts b/backend/src/services/org-membership/org-membership-dal.ts index 1fbe03f4b..ebf1700d0 100644 --- a/backend/src/services/org-membership/org-membership-dal.ts +++ b/backend/src/services/org-membership/org-membership-dal.ts @@ -122,8 +122,8 @@ export const orgMembershipDALFactory = (db: TDbClient) => { .orWhere((qb) => { // lastInvitedAt is older than 1 week ago AND createdAt is younger than 1 month ago void qb - .where(`${TableName.OrgMembership}.lastInvitedAt`, "<", oneMonthAgo) - .where(`${TableName.OrgMembership}.createdAt`, ">", oneWeekAgo); + .where(`${TableName.OrgMembership}.lastInvitedAt`, "<", oneWeekAgo) + .where(`${TableName.OrgMembership}.createdAt`, ">", oneMonthAgo); }); return memberships; @@ -135,9 +135,22 @@ export const orgMembershipDALFactory = (db: TDbClient) => { } }; + const updateLastInvitedAtByIds = async (membershipIds: string[]) => { + try { + if (membershipIds.length === 0) return; + await db(TableName.OrgMembership).whereIn("id", membershipIds).update({ lastInvitedAt: new Date() }); + } catch (error) { + throw new DatabaseError({ + error, + name: "Update last invited at by ids" + }); + } + }; + return { ...orgMembershipOrm, findOrgMembershipById, - findRecentInvitedMemberships + findRecentInvitedMemberships, + updateLastInvitedAtByIds }; }; diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 195ddae3e..eea37e627 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -109,7 +109,12 @@ type TOrgServiceFactoryDep = { projectKeyDAL: Pick; orgMembershipDAL: Pick< TOrgMembershipDALFactory, - "findOrgMembershipById" | "findOne" | "findById" | "findRecentInvitedMemberships" | "updateById" + | "findOrgMembershipById" + | "findOne" + | "findById" + | "findRecentInvitedMemberships" + | "updateById" + | "updateLastInvitedAtByIds" >; incidentContactDAL: TIncidentContactsDALFactory; samlConfigDAL: Pick; @@ -763,6 +768,10 @@ export const orgServiceFactory = ({ } }); + await orgMembershipDAL.updateById(inviteeOrgMembership.id, { + lastInvitedAt: new Date() + }); + return { signupToken: undefined }; }; @@ -1464,12 +1473,10 @@ export const orgServiceFactory = ({ } }); } - - await orgMembershipDAL.updateById(invitedUser.id, { - lastInvitedAt: new Date() - }); }) ); + + await orgMembershipDAL.updateLastInvitedAtByIds(invitedUsers.map((u) => u.id)); }; return { From 9d39910152413cec6c511cd96597800b251fc2e8 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 7 Jul 2025 15:35:49 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Minor=20fix=20to=20prevent=20setting=20last?= =?UTF-8?q?InvitedAt=20for=20invitees=20who=20weren=E2=80=99t=20actually?= =?UTF-8?q?=20sent=20an=20invitation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/services/org/org-service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index eea37e627..5941eb013 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -1442,6 +1442,7 @@ export const orgServiceFactory = ({ const appCfg = getConfig(); const orgCache: Record = {}; + const notifiedUsers: string[] = []; await Promise.all( invitedUsers.map(async (invitedUser) => { @@ -1472,11 +1473,12 @@ export const orgServiceFactory = ({ callback_url: `${appCfg.SITE_URL}/signupinvite` } }); + notifiedUsers.push(invitedUser.id); } }) ); - await orgMembershipDAL.updateLastInvitedAtByIds(invitedUsers.map((u) => u.id)); + await orgMembershipDAL.updateLastInvitedAtByIds(notifiedUsers); }; return {