diff --git a/apps/api/src/routes/monitors.ts b/apps/api/src/routes/monitors.ts index cf24334..e481b99 100644 --- a/apps/api/src/routes/monitors.ts +++ b/apps/api/src/routes/monitors.ts @@ -42,11 +42,16 @@ export const monitors = new Elysia({ prefix: "/monitors" }) return { error: `Minimum interval for ${plan} plan is ${limits.minIntervalS}s` }; } + // Enforce region limit for plan + const regions = body.regions ?? []; + if (regions.length > limits.maxRegions) { + set.status = 400; + return { error: `Free plan allows ${limits.maxRegions} region per monitor. Upgrade to use multi-region.` }; + } + // SSRF protection const ssrfError = await validateMonitorUrl(body.url); if (ssrfError) { set.status = 400; return { error: ssrfError }; } - - const regions = body.regions ?? []; const [monitor] = await sql` INSERT INTO monitors (account_id, name, url, method, request_headers, request_body, timeout_ms, interval_s, query, regions) VALUES ( @@ -89,6 +94,12 @@ export const monitors = new Elysia({ prefix: "/monitors" }) } } + // Enforce region limit for plan + if (body.regions && body.regions.length > limits.maxRegions) { + set.status = 400; + return { error: `Free plan allows ${limits.maxRegions} region per monitor. Upgrade to use multi-region.` }; + } + // SSRF protection on URL change if (body.url) { const ssrfError = await validateMonitorUrl(body.url); diff --git a/apps/api/src/utils/plans.ts b/apps/api/src/utils/plans.ts index 5f224bb..4adb018 100644 --- a/apps/api/src/utils/plans.ts +++ b/apps/api/src/utils/plans.ts @@ -3,20 +3,24 @@ export type Plan = "free" | "pro" | "lifetime"; export interface PlanLimits { maxMonitors: number; minIntervalS: number; + maxRegions: number; } const PLANS: Record = { free: { maxMonitors: 10, minIntervalS: 30, + maxRegions: 1, }, pro: { maxMonitors: 200, minIntervalS: 2, + maxRegions: 99, }, lifetime: { maxMonitors: 200, minIntervalS: 2, + maxRegions: 99, }, }; diff --git a/apps/web/src/views/landing.ejs b/apps/web/src/views/landing.ejs index 7121d92..d07b9ef 100644 --- a/apps/web/src/views/landing.ejs +++ b/apps/web/src/views/landing.ejs @@ -507,7 +507,7 @@
  • - No credit card + 1 region per monitor
  • diff --git a/apps/web/src/views/settings.ejs b/apps/web/src/views/settings.ejs index 7de7ea7..9fa55ea 100644 --- a/apps/web/src/views/settings.ejs +++ b/apps/web/src/views/settings.ejs @@ -6,7 +6,7 @@ const createdDate = new Date(it.account.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const plan = it.account.plan || 'free'; const planLabel = { free: 'Free', pro: 'Pro', lifetime: 'Lifetime' }[plan] || plan; - const limits = { free: { monitors: 10, interval: '30s' }, pro: { monitors: 200, interval: '2s' }, lifetime: { monitors: 200, interval: '2s' } }[plan] || { monitors: 10, interval: '30s' }; + const limits = { free: { monitors: 10, interval: '30s', regions: 1 }, pro: { monitors: 200, interval: '2s', regions: 'All' }, lifetime: { monitors: 200, interval: '2s', regions: 'All' } }[plan] || { monitors: 10, interval: '30s', regions: 1 }; %>
    @@ -35,8 +35,8 @@
    Min Interval
    -
    2
    -
    Regions
    +
    <%= limits.regions %>
    +
    Regions / Monitor
    <% if (plan === 'free') { %>