feat: free tier region limits
This commit is contained in:
parent
42b6aae54e
commit
40c4d3341c
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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') { %>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue