From 54d2d689c1d0334c931bd548ffa60a494dd8f34d Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 30 Apr 2026 14:38:03 -0700 Subject: [PATCH 1/4] Run messaging for delete in the background as well --- .../siteResource/deleteSiteResource.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/server/routers/siteResource/deleteSiteResource.ts b/server/routers/siteResource/deleteSiteResource.ts index df43d5c25..7dbb111ad 100644 --- a/server/routers/siteResource/deleteSiteResource.ts +++ b/server/routers/siteResource/deleteSiteResource.ts @@ -63,17 +63,26 @@ export async function deleteSiteResource( ); } - await db.transaction(async (trx) => { - // Delete the site resource - const [removedSiteResource] = await trx - .delete(siteResources) - .where(eq(siteResources.siteResourceId, siteResourceId)) - .returning(); + // Delete the site resource + const [removedSiteResource] = await db + .delete(siteResources) + .where(eq(siteResources.siteResourceId, siteResourceId)) + .returning(); + // Run in the background after the response is sent. Wrapped in its + // own transaction so it always executes on the primary — avoiding any + // replica-lag issues while still allowing the HTTP response to return + // early. + db.transaction(async (trx) => { await rebuildClientAssociationsFromSiteResource( removedSiteResource, trx ); + }).catch((err) => { + logger.error( + `Error rebuilding client associations for site resource ${removedSiteResource!.siteResourceId}:`, + err + ); }); logger.info(`Deleted site resource ${siteResourceId}`); From db6e60d0a3ea205b546db2f8ebed0e5af22f3ba8 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 1 May 2026 10:48:09 -0700 Subject: [PATCH 2/4] Adjust language --- server/emails/templates/NotifyTrialExpiring.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/emails/templates/NotifyTrialExpiring.tsx b/server/emails/templates/NotifyTrialExpiring.tsx index 7cd6d30ac..7c712e278 100644 --- a/server/emails/templates/NotifyTrialExpiring.tsx +++ b/server/emails/templates/NotifyTrialExpiring.tsx @@ -64,7 +64,7 @@ export const NotifyTrialExpiring = ({ Some features and resources may now be - restricted or disconnected. To restore full + restricted. To restore full access and continue using all the features you had during your trial, please upgrade to a paid plan. @@ -85,7 +85,7 @@ export const NotifyTrialExpiring = ({ {orgName} will end on{" "} {trialEndsAt} {isLastDay - ? " — that's tomorrow!" + ? " - that's tomorrow!" : `, in ${daysRemaining} days`} . @@ -93,8 +93,7 @@ export const NotifyTrialExpiring = ({ After your trial ends, your account will be moved to the free plan and some - functionality may be restricted or your - sites may disconnect. + functionality may be restricted. From 3dfd7e8a43992948ea78bfaefcaf09d69387eb3a Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 1 May 2026 11:47:14 -0700 Subject: [PATCH 3/4] Update limits --- server/lib/billing/limitSet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/lib/billing/limitSet.ts b/server/lib/billing/limitSet.ts index ae9a18ffe..e45ae637d 100644 --- a/server/lib/billing/limitSet.ts +++ b/server/lib/billing/limitSet.ts @@ -25,7 +25,7 @@ export const tier1LimitSet: LimitSet = { export const tier2LimitSet: LimitSet = { [FeatureId.USERS]: { - value: 100, + value: 50, description: "Team limit" }, [FeatureId.SITES]: { @@ -48,7 +48,7 @@ export const tier2LimitSet: LimitSet = { export const tier3LimitSet: LimitSet = { [FeatureId.USERS]: { - value: 500, + value: 250, description: "Business limit" }, [FeatureId.SITES]: { From 53e096f7cb6d459b635c1001de9897633f13981a Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 1 May 2026 15:01:48 -0700 Subject: [PATCH 4/4] Allow deleting account with trial --- server/private/lib/billing/getOrgTierData.ts | 15 +++++++++------ server/routers/auth/deleteMyAccount.ts | 5 +++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/server/private/lib/billing/getOrgTierData.ts b/server/private/lib/billing/getOrgTierData.ts index 1dc9f83a4..9df9b3b74 100644 --- a/server/private/lib/billing/getOrgTierData.ts +++ b/server/private/lib/billing/getOrgTierData.ts @@ -19,12 +19,13 @@ import { eq, and, ne } from "drizzle-orm"; export async function getOrgTierData( orgId: string -): Promise<{ tier: Tier | null; active: boolean }> { +): Promise<{ tier: Tier | null; active: boolean; isTrial: boolean }> { let tier: Tier | null = null; let active = false; + let isTrial = false; if (build !== "saas") { - return { tier, active }; + return { tier, active, isTrial }; } try { @@ -35,7 +36,7 @@ export async function getOrgTierData( .limit(1); if (!org) { - return { tier, active }; + return { tier, active, isTrial }; } let orgIdToUse = org.orgId; @@ -44,7 +45,7 @@ export async function getOrgTierData( logger.warn( `Org ${orgId} is not a billing org and does not have a billingOrgId` ); - return { tier, active }; + return { tier, active, isTrial }; } orgIdToUse = org.billingOrgId; } @@ -57,7 +58,7 @@ export async function getOrgTierData( .limit(1); if (!customer) { - return { tier, active }; + return { tier, active, isTrial }; } // Query for active subscriptions that are not license type @@ -84,11 +85,13 @@ export async function getOrgTierData( tier = subscription.type; active = true; } + + isTrial = subscription.trial ?? false; } } catch (error) { // If org not found or error occurs, return null tier and inactive // This is acceptable behavior as per the function signature } - return { tier, active }; + return { tier, active, isTrial }; } diff --git a/server/routers/auth/deleteMyAccount.ts b/server/routers/auth/deleteMyAccount.ts index b824e582b..07bdf883d 100644 --- a/server/routers/auth/deleteMyAccount.ts +++ b/server/routers/auth/deleteMyAccount.ts @@ -104,8 +104,9 @@ export async function deleteMyAccount( (r) => r.isBillingOrg && r.isOwner )?.orgId; if (primaryOrgId) { - const { tier, active } = await getOrgTierData(primaryOrgId); - if (active && tier) { + const { tier, active, isTrial } = + await getOrgTierData(primaryOrgId); + if (active && tier && !isTrial) { return next( createHttpError( HttpCode.BAD_REQUEST,