mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-19 15:11:36 +00:00
Revert "fix(billing): one reminder at 1 day, guarded by an advisory lock"
Revert "fix(billing): stop the reminder claim from rewriting stale metadata" This reverts commits037230496and14e059784.
This commit is contained in:
@@ -1,42 +1,21 @@
|
||||
import { IS_CLOUD } from "@dokploy/server";
|
||||
import { db, dbUrl } from "@dokploy/server/db";
|
||||
import { db } from "@dokploy/server/db";
|
||||
import TrialExpiringEmail from "@dokploy/server/emails/emails/trial-expiring";
|
||||
import { sendEmail } from "@dokploy/server/verification/send-verification-email";
|
||||
import { render } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { scheduleJob } from "node-schedule";
|
||||
import postgres from "postgres";
|
||||
import type Stripe from "stripe";
|
||||
import { user } from "@/server/db/schema";
|
||||
import { getStripeClient, planFromPriceIds } from "@/server/utils/billing";
|
||||
import { WEBSITE_URL } from "@/server/utils/stripe";
|
||||
|
||||
const REMINDER_THRESHOLDS = [
|
||||
{ days: 3, metadataKey: "trialReminder3Sent" },
|
||||
{ days: 1, metadataKey: "trialReminder1Sent" },
|
||||
] as const;
|
||||
|
||||
// Cloud runs several replicas and node-schedule fires on each one, so without
|
||||
// this every trial user would get one email per replica. Advisory locks live
|
||||
// in the connection rather than the schema, so no migration is needed.
|
||||
const ADVISORY_LOCK_KEY = 4820260918;
|
||||
|
||||
const withAdvisoryLock = async <T>(run: () => Promise<T>) => {
|
||||
const sql = postgres(dbUrl, { max: 1 });
|
||||
try {
|
||||
const [row] =
|
||||
await sql`select pg_try_advisory_lock(${ADVISORY_LOCK_KEY}) as locked`;
|
||||
if (!row?.locked) return null;
|
||||
try {
|
||||
return await run();
|
||||
} finally {
|
||||
await sql`select pg_advisory_unlock(${ADVISORY_LOCK_KEY})`;
|
||||
}
|
||||
} finally {
|
||||
await sql.end({ timeout: 5 });
|
||||
}
|
||||
};
|
||||
|
||||
const PLAN_LABELS: Record<string, string> = {
|
||||
hobby: "Hobby",
|
||||
startup: "Startup",
|
||||
@@ -115,11 +94,9 @@ const processSubscription = async (
|
||||
);
|
||||
const plan = planFromPriceIds(priceIds);
|
||||
|
||||
// Marked before sending so a crash mid-send cannot resend on the next run.
|
||||
// Stripe merges metadata, so only the claimed key travels; spreading the
|
||||
// snapshot read at list time would rewrite concurrent changes.
|
||||
// Claim the reminder before sending so concurrent replicas don't double-send.
|
||||
await stripe.subscriptions.update(subscription.id, {
|
||||
metadata: { [threshold.metadataKey]: "sent" },
|
||||
metadata: { ...subscription.metadata, [threshold.metadataKey]: "sent" },
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -132,7 +109,7 @@ const processSubscription = async (
|
||||
});
|
||||
} catch (error) {
|
||||
await stripe.subscriptions.update(subscription.id, {
|
||||
metadata: { [threshold.metadataKey]: "" },
|
||||
metadata: { ...subscription.metadata, [threshold.metadataKey]: "" },
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
@@ -169,14 +146,7 @@ export const initTrialNotificationsCronJob = () => {
|
||||
|
||||
scheduleJob("trial-expiration-reminders", "0 14 * * *", async () => {
|
||||
try {
|
||||
const result = await withAdvisoryLock(processTrialExpirations);
|
||||
if (!result) {
|
||||
console.log("Trial reminders: another replica holds the lock");
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`Trial reminders: scanned ${result.scanned}, sent ${result.sent}`,
|
||||
);
|
||||
await processTrialExpirations();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Trial reminder job failed:",
|
||||
|
||||
Reference in New Issue
Block a user