feat: implement free/pro plan system with monitor and interval limits

This commit is contained in:
2026-03-18 22:40:45 +04:00
parent 11e6c593ad
commit c89b63bd97
11 changed files with 119 additions and 26 deletions
+5 -5
View File
@@ -16,14 +16,14 @@ function hashEmail(email: string): string {
return createHmac("sha256", EMAIL_HMAC_KEY).update(email.toLowerCase().trim()).digest("hex");
}
async function resolveKey(key: string): Promise<{ accountId: string; keyId: string | null } | null> {
const [account] = await sql`SELECT id FROM accounts WHERE key = ${key}`;
if (account) return { accountId: account.id, keyId: null };
async function resolveKey(key: string): Promise<{ accountId: string; keyId: string | null; plan: string } | null> {
const [account] = await sql`SELECT id, plan FROM accounts WHERE key = ${key}`;
if (account) return { accountId: account.id, keyId: null, plan: account.plan };
const [apiKey] = await sql`SELECT id, account_id FROM api_keys WHERE key = ${key}`;
const [apiKey] = await sql`SELECT k.id, k.account_id, a.plan FROM api_keys k JOIN accounts a ON a.id = k.account_id WHERE k.key = ${key}`;
if (apiKey) {
sql`UPDATE api_keys SET last_used_at = now() WHERE id = ${apiKey.id}`.catch(() => {});
return { accountId: apiKey.account_id, keyId: apiKey.id };
return { accountId: apiKey.account_id, keyId: apiKey.id, plan: apiKey.plan };
}
return null;
+6 -5
View File
@@ -114,7 +114,7 @@ function redirect(to: string) {
return new Response(null, { status: 302, headers: { Location: to } });
}
async function getAccountId(cookie: any, headers: any): Promise<{ accountId: string; keyId: string | null } | null> {
async function getAccountId(cookie: any, headers: any): Promise<{ accountId: string; keyId: string | null; plan: string } | null> {
const authHeader = headers["authorization"] ?? "";
const bearer = authHeader.match(/^bearer\s+(.+)$/i)?.[1]?.trim();
const key = cookie?.pingql_key?.value || bearer;
@@ -198,12 +198,13 @@ export const dashboard = new Elysia()
const keyId = resolved?.keyId ?? null;
if (!accountId) return redirect("/dashboard");
const [acc] = await sql`SELECT id, email_hash, created_at FROM accounts WHERE id = ${accountId}`;
const [acc] = await sql`SELECT id, email_hash, plan, created_at FROM accounts WHERE id = ${accountId}`;
const isSubKey = !!keyId;
const apiKeys = isSubKey ? [] : await sql`SELECT id, key, label, created_at, last_used_at FROM api_keys WHERE account_id = ${accountId} ORDER BY created_at DESC`;
const loginKey = isSubKey ? null : (cookie?.pingql_key?.value ?? null);
const [{ count: monitorCount }] = await sql`SELECT COUNT(*)::int as count FROM monitors WHERE account_id = ${accountId}`;
return html("settings", { nav: "settings", account: acc, apiKeys, accountId, loginKey, isSubKey });
return html("settings", { nav: "settings", account: acc, apiKeys, accountId, loginKey, isSubKey, monitorCount });
})
// New monitor
@@ -212,7 +213,7 @@ export const dashboard = new Elysia()
const accountId = resolved?.accountId ?? null;
const keyId = resolved?.keyId ?? null;
if (!accountId) return redirect("/dashboard");
return html("new", { nav: "monitors", scripts: ["/dashboard/query-builder.js"] });
return html("new", { nav: "monitors", plan: resolved?.plan || "free" });
})
// Home data endpoint for polling (monitor list change detection)
@@ -247,7 +248,7 @@ export const dashboard = new Elysia()
ORDER BY checked_at DESC LIMIT 100
`;
return html("detail", { nav: "monitors", monitor, pings, scripts: ["/dashboard/query-builder.js"] });
return html("detail", { nav: "monitors", monitor, pings, plan: resolved?.plan || "free" });
})
// Chart partial endpoint — returns just the latency chart SVG