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
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

50 changes: 46 additions & 4 deletions claim-db-worker/app/api/auth/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ export async function GET(request: NextRequest) {
);
}

// Validate project exists
// Validate project exists and get project data
let projectData;
try {
await validateProject(projectID);
projectData = await validateProject(projectID);
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
Expand All @@ -141,16 +142,57 @@ export async function GET(request: NextRequest) {
projectID,
tokenData.access_token
);

if (transferResult.success) {
// Fetch project details with user's token to get workspace ID
const projectDetailsRes = await fetch(
`https://api.prisma.io/v1/projects/${projectID}`,
{
headers: {
Authorization: `Bearer ${tokenData.access_token}`,
"Content-Type": "application/json",
},
}
);
const projectDetails = (await projectDetailsRes.json()) as {
data?: { workspace?: { id?: string } };
};
const workspaceId = (projectDetails.data?.workspace?.id ?? "").replace(
/^wksp_/,
""
);

// Fetch databases to get database ID
const databasesRes = await fetch(
`https://api.prisma.io/v1/projects/${projectID}/databases`,
{
headers: {
Authorization: `Bearer ${tokenData.access_token}`,
"Content-Type": "application/json",
},
}
);
const databases = (await databasesRes.json()) as {
data?: Array<{ id?: string }>;
};
const databaseId = (databases.data?.[0]?.id ?? "").replace(/^db_/, "");

await sendServerAnalyticsEvent(
"create_db:claim_successful",
{
"project-id": projectID,
"workspace-id": workspaceId,
"database-id": databaseId,
},
request
);
return redirectToSuccess(request, projectID);

const cleanProjectId = projectID.replace(/^proj_/, "");
return redirectToSuccess(
request,
cleanProjectId,
workspaceId,
databaseId
);
} else {
await sendServerAnalyticsEvent(
"create_db:claim_failed",
Expand Down
9 changes: 8 additions & 1 deletion claim-db-worker/app/success/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import { Suspense } from "react";
function SuccessContent() {
const searchParams = useSearchParams();
const projectID = searchParams.get("projectID");
const workspaceId = searchParams.get("workspaceId");
const databaseId = searchParams.get("databaseId");

const consoleUrl =
workspaceId && projectID && databaseId
? `https://console.prisma.io/${workspaceId}/${projectID}/${databaseId}`
: "https://console.prisma.io/";

return (
<div className="flex flex-col mt-32 items-center justify-center text-center px-4 sm:px-6">
Expand All @@ -23,7 +30,7 @@ function SuccessContent() {
</p>

<a
href="https://console.prisma.io/"
href={consoleUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-3 bg-[#24bfa7] hover:bg-[#16A394] text-white font-bold text-xl sm:text-xl lg:text-2xl border-none rounded-lg px-8 py-4 sm:px-10 sm:py-5 lg:px-12 lg:py-6 cursor-pointer shadow-lg transition-all duration-200 min-h-[44px] sm:min-h-[52px] lg:min-h-[60px] mx-auto"
Expand Down
167 changes: 83 additions & 84 deletions claim-db-worker/app/web/connect/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Check, Copy, Eye, EyeClosed, Lightbulb } from "lucide-react";
import { Check, Copy, Eye, EyeClosed, Lightbulb, Zap } from "lucide-react";
import React, { useState } from "react";
import { useDatabase } from "../DatabaseContext";
import { customToast } from "@/lib/custom-toast";
Expand All @@ -23,74 +23,50 @@ type BuildStep = {
code?: string;
};

type ConnectionType = "prisma" | "direct";
const buildSteps: BuildStep[] = [
{
title: "Install Dependencies",
content: null,
code: `npm install prisma tsx @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg dotenv pg`,
},
{
title: "Initialize Prisma",
content: null,
code: "npx prisma init",
},
{
title: "Set connection string in .env",
content: null,
code: 'DATABASE_URL="<your-connection-string>"',
},
{
title: "Pull the database schema",
content: null,
code: "npx prisma db pull",
},
{
title: "Generate Prisma Client",
content: null,
code: "npx prisma generate",
},
{
title: "Start querying",
content: <span>Import and use Prisma Client in your application</span>,
code: `import { PrismaClient } from "./generated/prisma/client.js";
import { PrismaPg } from "@prisma/adapter-pg";

const buildSteps: Record<ConnectionType, BuildStep[]> = {
prisma: [
{
title: "Install Prisma",
content: null,
code: "npm install prisma @prisma/client",
},
{
title: "Initialize Prisma",
content: null,
code: "npx prisma init",
},
{
title: "Set connection string in .env",
content: null,
code: 'DATABASE_URL="<your-connection-string>"',
},
{
title: "Pull the database schema",
content: null,
code: "npx prisma db pull",
},
{
title: "Generate Prisma Client",
content: null,
code: "npx prisma generate",
},
{
title: "Start querying",
content: <span>Import and use Prisma Client in your application</span>,
code: `import { PrismaClient } from "@prisma/client";
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL,
});

const prisma = new PrismaClient();
const users = await prisma.user.findMany();
console.log(users);`,
},
],
direct: [
{
title: "Install node-postgres",
content: null,
code: "npm install pg",
},
{
title: "Set connection string in .env",
content: null,
code: 'DATABASE_URL="<your-connection-string>"',
},
{
title: "Set up connection",
content: null,
code: `import { Pool } from "pg";
const prisma = new PrismaClient({
adapter,
});

const pool = new Pool({
connectionString: process.env.DATABASE_URL
});`,
},
{
title: "Query your database",
content: null,
code: `const { rows } = await pool.query('SELECT * FROM "User"');

console.log(rows);`,
},
],
};
export default prisma;`,
},
];

const StepItem = ({
number,
Expand Down Expand Up @@ -138,19 +114,18 @@ const StepItem = ({

export default function ConnectPage() {
const { dbInfo } = useDatabase();
const [connectionType, setConnectionType] =
useState<ConnectionType>("prisma");
const [copied, setCopied] = useState(false);
const [copiedDirect, setCopiedDirect] = useState(false);
const [copiedAccel, setCopiedAccel] = useState(false);
const [showPassword, setShowPassword] = useState(false);

const connectionString =
connectionType === "prisma"
? dbInfo.connectionString
: dbInfo.directConnectionString;
const connectionString = dbInfo.directConnectionString;

const handleCopyConnectionString = async () => {
const handleCopyConnectionString = async (
copyString: string,
setCopied: (copied: boolean) => void
) => {
try {
await navigator.clipboard.writeText(connectionString || "");
await navigator.clipboard.writeText(copyString);
setCopied(true);
customToast("success", "Connection string copied to clipboard");
setTimeout(() => setCopied(false), 2000);
Expand All @@ -162,7 +137,7 @@ export default function ConnectPage() {
return (
<div className="bg-code rounded-lg rounded-tl-none p-4 sm:p-6 border border-subtle flex flex-col h-full min-h-[calc(100vh-200px)]">
{/* Connection type toggle */}
<div className="flex flex-col sm:flex-row rounded-md p-1 w-full mb-3 gap-2">
{/* <div className="flex flex-col sm:flex-row rounded-md p-1 w-full mb-3 gap-2">
{(["prisma", "direct"] as const).map((type) => (
<button
key={type}
Expand All @@ -176,7 +151,7 @@ export default function ConnectPage() {
{type === "prisma" ? "With Prisma ORM" : "With any other tool"}
</button>
))}
</div>
</div> */}

{/* Connection string input */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 mb-6">
Expand Down Expand Up @@ -209,32 +184,56 @@ export default function ConnectPage() {
</button>
<button
className={`flex items-center justify-center w-12 h-12 border border-subtle rounded-md transition-colors ${
copied
copiedDirect
? "text-green-400 border-green-400"
: "text-muted hover:text-white"
}`}
onClick={handleCopyConnectionString}
onClick={() =>
handleCopyConnectionString(
dbInfo.directConnectionString,
setCopiedDirect
)
}
title="Copy connection string"
disabled={!connectionString}
>
{copied ? (
{copiedDirect ? (
<Check className="h-5 w-5" />
) : (
<Copy className="h-5 w-5" />
)}
</button>
<button
className={`flex items-center justify-center w-12 h-12 border border-subtle rounded-md transition-colors ${
copiedAccel
? "text-green-400 border-green-400"
: "text-muted hover:text-white"
}`}
onClick={() =>
handleCopyConnectionString(
dbInfo.connectionString,
setCopiedAccel
)
}
title="Copy accelerate connection string"
disabled={!connectionString}
>
{copiedAccel ? (
<Check className="h-5 w-5" />
) : (
<Zap className="h-5 w-5" />
)}
</button>
</div>
</div>

<div className="flex-1 flex flex-col">
<div className="space-y-6">
<h3 className="text-lg font-bold text-white mb-4">
{connectionType === "prisma"
? "Connect with Prisma ORM"
: "Connect with node-postgres"}
Connect with Prisma ORM
</h3>
<div className="space-y-4">
{buildSteps[connectionType].map((step, index) => (
{buildSteps.map((step, index) => (
<StepItem
key={index}
number={index + 1}
Expand Down
1 change: 0 additions & 1 deletion claim-db-worker/lib/prismaSchemaEditor/defaultSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ generator client {

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}`;
22 changes: 20 additions & 2 deletions claim-db-worker/lib/project-transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { TokenData } from "./auth-utils";
export async function transferProject(
projectID: string,
accessToken: string
): Promise<{ success: boolean; error?: string; status?: number }> {
): Promise<{
success: boolean;
error?: string;
status?: number;
transferResponse: any;
}> {
const env = getEnv();

const requestBody = JSON.stringify({
Expand All @@ -24,13 +29,26 @@ export async function transferProject(
);

if (transferResponse.ok) {
return { success: true };
const responseText = await transferResponse.text();
let responseData = null;

if (responseText) {
try {
responseData = JSON.parse(responseText);
} catch (e) {
console.log("Transfer response (not JSON):", responseText);
responseData = { rawResponse: responseText };
}
}

return { success: true, transferResponse: responseData };
} else {
const responseText = await transferResponse.text();
return {
success: false,
error: responseText,
status: transferResponse.status,
transferResponse: null,
};
}
}
6 changes: 5 additions & 1 deletion claim-db-worker/lib/response-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ export function redirectToError(

export function redirectToSuccess(
request: NextRequest,
projectID: string
projectID: string,
workspaceId: string,
databaseId: string
): Response {
const params = new URLSearchParams({
projectID: projectID,
workspaceId: workspaceId,
databaseId: databaseId,
});

const baseUrl = getBaseUrl(request);
Expand Down
Loading