feat: free tier region limits

This commit is contained in:
nate 2026-03-22 05:24:01 +04:00
parent 42b6aae54e
commit 40c4d3341c
4 changed files with 21 additions and 6 deletions

View File

@ -42,11 +42,16 @@ export const monitors = new Elysia({ prefix: "/monitors" })
return { error: `Minimum interval for ${plan} plan is ${limits.minIntervalS}s` }; 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 // SSRF protection
const ssrfError = await validateMonitorUrl(body.url); const ssrfError = await validateMonitorUrl(body.url);
if (ssrfError) { set.status = 400; return { error: ssrfError }; } if (ssrfError) { set.status = 400; return { error: ssrfError }; }
const regions = body.regions ?? [];
const [monitor] = await sql` const [monitor] = await sql`
INSERT INTO monitors (account_id, name, url, method, request_headers, request_body, timeout_ms, interval_s, query, regions) INSERT INTO monitors (account_id, name, url, method, request_headers, request_body, timeout_ms, interval_s, query, regions)
VALUES ( 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 // SSRF protection on URL change
if (body.url) { if (body.url) {
const ssrfError = await validateMonitorUrl(body.url); const ssrfError = await validateMonitorUrl(body.url);

View File

@ -3,20 +3,24 @@ export type Plan = "free" | "pro" | "lifetime";
export interface PlanLimits { export interface PlanLimits {
maxMonitors: number; maxMonitors: number;
minIntervalS: number; minIntervalS: number;
maxRegions: number;
} }
const PLANS: Record<Plan, PlanLimits> = { const PLANS: Record<Plan, PlanLimits> = {
free: { free: {
maxMonitors: 10, maxMonitors: 10,
minIntervalS: 30, minIntervalS: 30,
maxRegions: 1,
}, },
pro: { pro: {
maxMonitors: 200, maxMonitors: 200,
minIntervalS: 2, minIntervalS: 2,
maxRegions: 99,
}, },
lifetime: { lifetime: {
maxMonitors: 200, maxMonitors: 200,
minIntervalS: 2, minIntervalS: 2,
maxRegions: 99,
}, },
}; };

View File

@ -507,7 +507,7 @@
</li> </li>
<li class="flex items-center gap-2"> <li class="flex items-center gap-2">
<svg class="w-4 h-4 text-green-400 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg> <svg class="w-4 h-4 text-green-400 shrink-0" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
No credit card 1 region per monitor
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -6,7 +6,7 @@
const createdDate = new Date(it.account.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const createdDate = new Date(it.account.created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
const plan = it.account.plan || 'free'; const plan = it.account.plan || 'free';
const planLabel = { free: 'Free', pro: 'Pro', lifetime: 'Lifetime' }[plan] || plan; 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 };
%> %>
<main class="max-w-3xl mx-auto px-8 py-10 space-y-8"> <main class="max-w-3xl mx-auto px-8 py-10 space-y-8">
@ -35,8 +35,8 @@
<div class="text-xs text-gray-500">Min Interval</div> <div class="text-xs text-gray-500">Min Interval</div>
</div> </div>
<div> <div>
<div class="text-lg font-semibold text-gray-200">2</div> <div class="text-lg font-semibold text-gray-200"><%= limits.regions %></div>
<div class="text-xs text-gray-500">Regions</div> <div class="text-xs text-gray-500">Regions / Monitor</div>
</div> </div>
</div> </div>
<% if (plan === 'free') { %> <% if (plan === 'free') { %>