Skip to content

Commit

Permalink
feat: new setup for lifetime (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminshafii authored Dec 10, 2024
1 parent 9f0edda commit 5440ad4
Show file tree
Hide file tree
Showing 22 changed files with 2,142 additions and 150 deletions.
3 changes: 1 addition & 2 deletions web/app/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use server";
import { auth, clerkClient } from "@clerk/nextjs/server";
import { auth } from "@clerk/nextjs/server";
import { Unkey } from "@unkey/api";
import { db, UserUsageTable as UserUsageTableImport } from "@/drizzle/schema";
import { eq } from "drizzle-orm";
Expand Down Expand Up @@ -33,7 +33,6 @@ export async function createLicenseKeyFromUserId(userId: string) {
const name = "my api key";
const unkey = new Unkey({ token });


console.log("Creating Unkey license key");
const key = await unkey.keys.create({
name: name,
Expand Down
87 changes: 87 additions & 0 deletions web/app/api/cron/redeploy/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { NextResponse } from "next/server";
import { db, vercelTokens } from "@/drizzle/schema";
import { Vercel } from "@vercel/sdk";
import { headers } from "next/headers";

const CRON_SECRET = process.env.CRON_SECRET;

export async function GET(request: Request) {
console.log("Redeploy cron job started");
// Verify the request is from Vercel Cron
const headersList = headers();
const authHeader = headersList.get('authorization');

if (authHeader !== `Bearer ${CRON_SECRET}`) {
return new NextResponse('Unauthorized', { status: 401 });
}

try {
// Get all tokens from the database
const tokens = await db
.select()
.from(vercelTokens);

console.log(`Found ${tokens.length} tokens to process`);

const results = await Promise.allSettled(
tokens.map(async (tokenRecord) => {
try {
const vercel = new Vercel({
bearerToken: tokenRecord.token,
});

if (!tokenRecord.projectId) {
console.log(`No project ID for user ${tokenRecord.userId}`);
return;
}
const repo = "file-organizer-2000";
const org = "different-ai";
const ref = "master";

// Create new deployment with correct properties
const deployment = await vercel.deployments.createDeployment({
requestBody: {
name: `file-organizer-redeploy-${Date.now()}`,
target: "production",
project: tokenRecord.projectId,
gitSource: {
type: "github",
repo: repo,
ref: ref,
org: org,
},
projectSettings: {
framework: "nextjs",
buildCommand: "pnpm build:self-host",
installCommand: "pnpm install",
outputDirectory: ".next",
rootDirectory: "web",
},
},
});

console.log(`Redeployed project ${tokenRecord.projectId} for user ${tokenRecord.userId}`);
return deployment;
} catch (error) {
console.error(`Failed to redeploy for user ${tokenRecord.userId}:`, error);
throw error;
}
})
);

const successful = results.filter((r) => r.status === "fulfilled").length;
const failed = results.filter((r) => r.status === "rejected").length;

return NextResponse.json({
message: `Processed ${tokens.length} tokens`,
stats: {
total: tokens.length,
successful,
failed,
},
});
} catch (error) {
console.error("Error in redeploy cron:", error);
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
}
}
63 changes: 63 additions & 0 deletions web/app/api/redeploy/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { NextResponse } from "next/server";
import { db, vercelTokens } from "@/drizzle/schema";
import { Vercel } from "@vercel/sdk";
import { auth } from "@clerk/nextjs/server";
import { eq } from "drizzle-orm";

export async function POST() {
try {
const { userId } = auth();
if (!userId) {
return new NextResponse("Unauthorized", { status: 401 });
}

const tokenRecord = await db
.select()
.from(vercelTokens)
.where(eq(vercelTokens.userId, userId))
.limit(1);

if (!tokenRecord[0] || !tokenRecord[0].projectId) {
return new NextResponse("No deployment found", { status: 404 });
}

const vercel = new Vercel({
bearerToken: tokenRecord[0].token,
});
const repo = "file-organizer-2000";
const org = "different-ai";
const ref = "master";

const deployment = await vercel.deployments.createDeployment({
requestBody: {
name: `file-organizer-redeploy-${Date.now()}`,
target: "production",
project: tokenRecord[0].projectId,
gitSource: {
type: "github",
repo: repo,
ref: ref,
org: org,
},
projectSettings: {
framework: "nextjs",
buildCommand: "pnpm build:self-host",
installCommand: "pnpm install",
outputDirectory: ".next",
rootDirectory: "web",
},
},
});

return NextResponse.json({
success: true,
deploymentUrl: deployment.url,
});
} catch (error) {
console.error("Error in redeploy:", error);
return NextResponse.json(
{ error: "Failed to redeploy" },
{ status: 500 }
);
}
}
172 changes: 172 additions & 0 deletions web/app/dashboard/lifetime/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
"use server";
import { Vercel } from "@vercel/sdk";
import { auth } from "@clerk/nextjs/server";
import { db, vercelTokens } from "@/drizzle/schema";
import { eq, exists } from "drizzle-orm";
import { createLicenseKeyFromUserId } from "@/app/actions";

const GITHUB_ORG = "different-ai";
const GITHUB_REPO = "file-organizer-2000";
const GITHUB_BRANCH = "master";

type SetupProjectResult = {
success: boolean;
deploymentUrl: string;
projectId: string;
licenseKey: string;
projectUrl: string;
};

export async function setupProject(
vercelToken: string,
openaiKey: string
): Promise<SetupProjectResult> {
const { userId } = auth();
// create an api key for the user
if (!userId) {
throw new Error("User not authenticated");
}
const apiKey = await createLicenseKeyFromUserId(userId);

if (!vercelToken) {
throw new Error("Vercel token is required");
}

if (!openaiKey) {
throw new Error("OpenAI API key is required");
}
console.log("userId", userId);

// Store or update the token
const existingToken = await db
.select()
.from(vercelTokens)
.where(eq(vercelTokens.userId, userId));

console.log("existingToken", existingToken);

if (existingToken.length > 0) {
console.log("Updating existing token", vercelToken);
// Update existing token
await db
.update(vercelTokens)
.set({ token: vercelToken, updatedAt: new Date() })
.where(eq(vercelTokens.userId, userId));
} else {
console.log("Inserting new token");
// Insert new token
await db.insert(vercelTokens).values({
userId,
token: vercelToken,
});
}

const vercel = new Vercel({
bearerToken: vercelToken,
});

console.log("Starting setupProject process");
const uniqueProjectName = `file-organizer-${Math.random()
.toString(36)
.substring(2, 15)}`;

const projectUrl = `https://${uniqueProjectName}.vercel.app`;

console.log("apiKey", apiKey.key.key);
try {
// Create project with required settings
console.log("Creating new project...");
const createProjectResponse = await vercel.projects.createProject({
requestBody: {
name: uniqueProjectName,
rootDirectory: "web",
publicSource: true,
framework: "nextjs",
buildCommand: "pnpm build:self-host",
installCommand: "pnpm install",
outputDirectory: ".next",
environmentVariables: [
{
key: "SOLO_API_KEY",
value: apiKey.key.key,
type: "plain",
target: "production",
},
{
key: "OPENAI_API_KEY",
type: "plain",
value: openaiKey,
target: "production",
},
],
},
});
console.log(`✅ Project created successfully: ${createProjectResponse.id}`);

// Create deployment with project settings
console.log("Creating deployment...");
const deploymentResponse = await vercel.deployments.createDeployment({
requestBody: {
name: uniqueProjectName,
target: "production",
gitSource: {
type: "github",
repo: GITHUB_REPO,
ref: GITHUB_BRANCH,
org: GITHUB_ORG,
},
projectSettings: {
framework: "nextjs",
buildCommand: "pnpm build:self-host",
installCommand: "pnpm install",
outputDirectory: ".next",
rootDirectory: "web",
},
},
});

// Update token record with project details and URL
await db
.update(vercelTokens)
.set({
projectId: createProjectResponse.id,
deploymentUrl: deploymentResponse.url,
projectUrl,
updatedAt: new Date(),
})
.where(eq(vercelTokens.userId, userId));

return {
success: true,
deploymentUrl: deploymentResponse.url,
projectId: createProjectResponse.id,
licenseKey: apiKey.key.key,
projectUrl,
};
} catch (error: any) {
console.error("❌ Error in setupProject:", error);
throw error;
}
}

// Helper function to get user's Vercel deployment info
export async function getVercelDeployment() {
const { userId } = auth();
if (!userId) {
throw new Error("User not authenticated");
}

const tokenRecord = await db
.select()
.from(vercelTokens)
.where(eq(vercelTokens.userId, userId))
.limit(1);

return tokenRecord[0]
? {
deploymentUrl: tokenRecord[0].deploymentUrl,
projectId: tokenRecord[0].projectId,
projectUrl: tokenRecord[0].projectUrl,
}
: null;
}
Loading

0 comments on commit 5440ad4

Please sign in to comment.