diff --git a/claim-db-worker/README.md b/claim-db-worker/README.md index 1088ad6..ba33ec2 100644 --- a/claim-db-worker/README.md +++ b/claim-db-worker/README.md @@ -1,101 +1,11 @@ -# Claim DB Worker +# Claim DB -A Cloudflare Worker for claiming Prisma databases. This worker handles OAuth authentication with Prisma and transfers database projects to authenticated users. +A Next.js app for claiming and managing Prisma databases, deployed on Cloudflare Workers via OpenNext. ## Features -- ✅ OAuth authentication with Prisma -- ✅ Database project claiming -- ✅ Rate limiting (100 requests per minute) -- ✅ PostHog analytics tracking -- ✅ Cloudflare Analytics Engine integration -- ✅ Error handling and user feedback -- ✅ Responsive UI with Tailwind CSS - -## Quick Start - -### Development - -1. **Install dependencies:** - ```bash - npm install - ``` - -2. **Set up environment variables:** - Create a `.env.local` file: - ```env - CLIENT_ID=your_prisma_client_id - CLIENT_SECRET=your_prisma_client_secret - INTEGRATION_TOKEN=your_prisma_integration_token - POSTHOG_API_KEY=your_posthog_api_key - POSTHOG_API_HOST=https://app.posthog.com - ``` - -3. **Start development server:** - ```bash - npm run dev - ``` - -4. **Test the flow:** - Visit `http://localhost:3000/?projectID=test123` - -### Production Deployment - -1. **Set up Cloudflare secrets:** - ```bash - wrangler secret put CLIENT_ID - wrangler secret put CLIENT_SECRET - wrangler secret put INTEGRATION_TOKEN - wrangler secret put POSTHOG_API_KEY - wrangler secret put POSTHOG_API_HOST - ``` - -2. **Build and deploy:** - ```bash - npm run deploy - ``` - -## API Endpoints - -- **`/api/claim`** - Generates OAuth URLs and tracks page views -- **`/api/auth/callback`** - Handles OAuth callback and project transfer -- **`/api/test`** - Rate limit testing endpoint -- **`/api/success-test`** - Test endpoint for success page - -## Pages - -- **`/`** - Homepage (redirects to claim flow if projectID provided) -- **`/claim`** - Claim page with OAuth button -- **`/success`** - Success page after claiming -- **`/error`** - Error page for various error states - -## Flow - -1. User visits `/?projectID=123` -2. Homepage redirects to `/api/claim?projectID=123` -3. API generates OAuth URL and redirects to `/claim?projectID=123&authUrl=...` -4. User clicks OAuth, redirects to `/api/auth/callback` -5. API exchanges code for token and transfers project -6. API redirects to `/success?projectID=123` - -## Configuration - -The project uses Cloudflare Worker bindings: -- Rate limiting via `CLAIM_DB_RATE_LIMITER` binding -- Analytics via `CREATE_DB_DATASET` binding -- Environment variables as secrets - -See `wrangler.jsonc` for the complete configuration. - -## Development vs Production - -- **Development**: Uses `process.env` with graceful fallbacks for rate limiting and analytics -- **Production**: Uses Cloudflare Worker bindings via `globalThis` - -The `lib/env.ts` utility handles this automatically. - -## Testing - -- **Local testing**: `npm run dev` then visit with projectID -- **Rate limit testing**: Visit `/api/test` -- **Success page testing**: Visit `/api/success-test` +- Claim Prisma databases with one click +- Monaco-based Prisma schema editor +- Deployed globally on Cloudflare's edge network +- Prisma Studio embed +- Built with Next.js and React diff --git a/claim-db-worker/app/api/auth/url/route.ts b/claim-db-worker/app/api/auth/url/route.ts new file mode 100644 index 0000000..fed801e --- /dev/null +++ b/claim-db-worker/app/api/auth/url/route.ts @@ -0,0 +1,34 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request: NextRequest) { + const body: { redirectUri: string } = await request.json(); + const redirectUri = body.redirectUri; + const clientId = process.env.CLIENT_ID; + + if (!clientId) { + return NextResponse.json( + { error: "Client ID not configured" }, + { status: 500 } + ); + } + + const searchParams = new URLSearchParams(); + searchParams.set("client_id", clientId); + searchParams.set("redirect_uri", redirectUri.toString()); + searchParams.set("response_type", "code"); + searchParams.set("scope", "workspace:admin"); + searchParams.set("state", generateState()); + searchParams.set("utm_source", "create-db-frontend"); + searchParams.set("utm_medium", "claim_button"); + + const authUrl = `https://auth.prisma.io/authorize?${searchParams.toString()}`; + + return NextResponse.json({ authUrl }); +} + +function generateState(): string { + return ( + Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15) + ); +} diff --git a/claim-db-worker/app/claim/page.tsx b/claim-db-worker/app/claim/page.tsx index 3f73253..561b826 100644 --- a/claim-db-worker/app/claim/page.tsx +++ b/claim-db-worker/app/claim/page.tsx @@ -9,11 +9,9 @@ function ClaimContent() { const searchParams = useSearchParams(); const router = useRouter(); const projectID = searchParams.get("projectID"); - const utmSource = searchParams.get("utm_source"); - const utmMedium = searchParams.get("utm_medium"); const [isLoading, setIsLoading] = useState(false); - if (!projectID && !window.location.pathname.includes('/test/')) { + if (!projectID && !window.location.pathname.includes("/test/")) { router.push("/"); return null; } @@ -21,30 +19,29 @@ function ClaimContent() { const redirectUri = new URL("/api/auth/callback", window.location.origin); redirectUri.searchParams.set("projectID", projectID!); - function generateState(): string { - return ( - Math.random().toString(36).substring(2, 15) + - Math.random().toString(36).substring(2, 15) - ); - } - - const authParams = new URLSearchParams({ - client_id: process.env.NEXT_PUBLIC_CLIENT_ID!, - redirect_uri: redirectUri.toString(), - response_type: "code", - scope: "workspace:admin", - state: generateState(), - utm_source: utmSource || "unknown", - utm_medium: utmMedium || "unknown", - }); + const handleClaimClick = async () => { + try { + setIsLoading(true); + const response = await fetch("/api/auth/url", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ redirectUri: redirectUri.toString() }), + }); - const authUrl = `https://auth.prisma.io/authorize?${authParams.toString()}`; + if (!response.ok) { + throw new Error("Failed to get auth URL"); + } - const handleClaimClick = () => { - if (authUrl) { - setIsLoading(true); - window.open(authUrl, "_blank"); - setTimeout(() => setIsLoading(false), 1000); + const data = (await response.json()) as { authUrl: string }; + if (data.authUrl) { + window.open(data.authUrl, "_blank"); + } + } catch (error) { + console.error("Error:", error); + } finally { + setIsLoading(false); } }; @@ -60,7 +57,7 @@ function ClaimContent() {