refactor: flatten monorepo structure to backend/ frontend/ devops/
Rename subdirectories for a cleaner single-repo layout: - website-monitoring-backend/ → backend/ - website-monitoring-frontend/ → frontend/ - website-monitoring-devops/ → devops/ Update all references in package.json scripts, CI workflows, docker-compose, pre-commit hooks, and documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getSupabaseAdmin } from "@/lib/admin";
|
||||
import { TIER_LIMITS } from "@/services/tierLimits";
|
||||
import { requireOrgMembership } from "@/lib/apiAuth";
|
||||
|
||||
/**
|
||||
* GET /api/billing/usage
|
||||
*
|
||||
* Returns current usage vs tier limits for an organization.
|
||||
* Requires authenticated user who is a member of the organization.
|
||||
* Query params: ?organizationId=xxx
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const organizationId = url.searchParams.get("organizationId");
|
||||
|
||||
if (!organizationId) {
|
||||
return NextResponse.json({ error: "organizationId required" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Verify caller belongs to this organization
|
||||
const auth = await requireOrgMembership(organizationId, request);
|
||||
if (auth instanceof NextResponse) return auth;
|
||||
|
||||
const supabase = getSupabaseAdmin();
|
||||
|
||||
// Get organization with tier info
|
||||
const { data: org, error: orgError } = await supabase
|
||||
.from("organizations")
|
||||
.select("id, name, subscription_tier, subscription_status, created_at")
|
||||
.eq("id", organizationId)
|
||||
.single();
|
||||
|
||||
if (orgError || !org) {
|
||||
return NextResponse.json({ error: "Organization not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const tier = String(org.subscription_tier || "free");
|
||||
const limits = TIER_LIMITS[tier] || TIER_LIMITS.free;
|
||||
|
||||
// Get current usage (parallel queries)
|
||||
const startOfMonth = new Date();
|
||||
startOfMonth.setDate(1);
|
||||
startOfMonth.setHours(0, 0, 0, 0);
|
||||
|
||||
const [
|
||||
{ count: websiteCount },
|
||||
{ count: memberCount },
|
||||
{ count: scanCountThisMonth },
|
||||
{ count: scanCountTotal },
|
||||
{ count: alertCount },
|
||||
] = await Promise.all([
|
||||
supabase.from("websites").select("*", { count: "exact", head: true }).eq("organization_id", organizationId),
|
||||
supabase.from("organization_members").select("*", { count: "exact", head: true }).eq("organization_id", organizationId),
|
||||
supabase
|
||||
.from("scans")
|
||||
.select("*", { count: "exact", head: true })
|
||||
.gte("created_at", startOfMonth.toISOString()),
|
||||
supabase.from("scans").select("*", { count: "exact", head: true }),
|
||||
supabase.from("alerts").select("*", { count: "exact", head: true }).eq("status", "active"),
|
||||
]);
|
||||
|
||||
const usage = {
|
||||
websites: { used: websiteCount || 0, limit: limits.websites, percentage: limits.websites === -1 ? 0 : Math.round(((websiteCount || 0) / limits.websites) * 100) },
|
||||
scansThisMonth: { used: scanCountThisMonth || 0, limit: limits.scansPerMonth, percentage: limits.scansPerMonth === -1 ? 0 : Math.round(((scanCountThisMonth || 0) / limits.scansPerMonth) * 100) },
|
||||
teamMembers: { used: memberCount || 0, limit: limits.teamMembers, percentage: limits.teamMembers === -1 ? 0 : Math.round(((memberCount || 0) / limits.teamMembers) * 100) },
|
||||
totalScans: scanCountTotal || 0,
|
||||
activeAlerts: alertCount || 0,
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
organization: {
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
tier,
|
||||
status: org.subscription_status || "active",
|
||||
createdAt: org.created_at,
|
||||
},
|
||||
plan: limits,
|
||||
usage,
|
||||
features: {
|
||||
scheduledScans: limits.scheduledScans,
|
||||
alertNotifications: limits.alertNotifications,
|
||||
competitorAnalysis: limits.competitorAnalysis,
|
||||
apiAccess: limits.apiAccess,
|
||||
prioritySupport: limits.prioritySupport,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : "Unknown error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user