Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions app/api/ads/v1/earnings/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// /api/ads/v1/earnings — the account's ad money and delivery, for a token caller.
//
// GET ?days=7|30|90|365
//
// The same model /dashboard/ads/earnings renders, for something holding an API
// token. It exists because the alternative for a client that wants fleet totals
// is one /campaigns/[id] request per campaign, and the account is past 170 of
// them — see `crawlproof dashboard`, which polls this on a timer.
//
// Same auth as the rest of /api/ads/v1/*: `Authorization: Bearer crp_…`.

import { NextResponse, type NextRequest } from "next/server";

import { serviceClient } from "@/lib/supabase/service";
import { authenticateBearer } from "@/lib/sp/apiAuth";
import { loadEarnings } from "@/lib/ads/earnings-data";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

const ALLOWED_DAYS = [7, 30, 90, 365];

/** An unknown window falls back to 30 rather than reaching the query planner. */
export function parseDays(raw: string | null): number {
const n = Number(raw);
return ALLOWED_DAYS.includes(n) ? n : 30;
}

export async function GET(req: NextRequest) {
const auth = await authenticateBearer(req);
if (!auth.ok) return NextResponse.json({ error: auth.error }, { status: auth.status });

const days = parseDays(req.nextUrl.searchParams.get("days"));
// The service client has no RLS. loadEarnings filters every table by
// owner_id itself, which is what makes passing it here safe.
const model = await loadEarnings(serviceClient(), auth.userId, days);
return NextResponse.json(model);
}
33 changes: 33 additions & 0 deletions app/api/tracker/v1/sites/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// /api/tracker/v1/sites — the projects this token can read stats for.
//
// GET → { sites: [{ id, name, url, tracker_enabled }] }
//
// /stats already names them, but only inside the 400 it returns when the
// account has more than one and the caller did not say which. A client that
// wants the whole fleet should not have to parse an error message to find it.

import { NextResponse, type NextRequest } from "next/server";

import { serviceClient } from "@/lib/supabase/service";
import { authenticateBearer } from "@/lib/sp/apiAuth";
import { listProjects } from "@/lib/tracker/apiStats";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

export async function GET(req: NextRequest) {
const auth = await authenticateBearer(req);
if (!auth.ok) return NextResponse.json({ error: auth.error }, { status: auth.status });

const listed = await listProjects(serviceClient(), auth.userId);
if (!listed.ok) return NextResponse.json({ error: listed.error }, { status: listed.status });

return NextResponse.json({
sites: listed.projects.map((p) => ({
id: p.id,
name: p.name,
url: p.url,
tracker_enabled: p.tracker_enabled ?? null,
})),
});
}
Loading
Loading