Skip to content

Commit

Permalink
feat(web): add deployment dashboard (#284)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminshafii authored Dec 11, 2024
1 parent 5440ad4 commit 4f39cc5
Show file tree
Hide file tree
Showing 25 changed files with 2,007 additions and 21 deletions.
3 changes: 2 additions & 1 deletion web/app/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use server";
import { auth } 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,6 +33,7 @@ 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
25 changes: 18 additions & 7 deletions web/app/api/cron/redeploy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
import { db, vercelTokens } from "@/drizzle/schema";
import { Vercel } from "@vercel/sdk";
import { headers } from "next/headers";
import { eq } from "drizzle-orm";

const CRON_SECRET = process.env.CRON_SECRET;

Expand All @@ -23,6 +24,10 @@ export async function GET(request: Request) {

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

const repo = "file-organizer-2000";
const org = "different-ai";
const ref = "master";

const results = await Promise.allSettled(
tokens.map(async (tokenRecord) => {
try {
Expand All @@ -34,21 +39,18 @@ export async function GET(request: Request) {
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
// Create new deployment
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,
repo,
ref,
org,
},
projectSettings: {
framework: "nextjs",
Expand All @@ -60,6 +62,15 @@ export async function GET(request: Request) {
},
});

// Update last deployment timestamp
await db
.update(vercelTokens)
.set({
lastDeployment: new Date(),
updatedAt: new Date(),
})
.where(eq(vercelTokens.userId, tokenRecord.userId));

console.log(`Redeployed project ${tokenRecord.projectId} for user ${tokenRecord.userId}`);
return deployment;
} catch (error) {
Expand Down
57 changes: 57 additions & 0 deletions web/app/api/deployment/status/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { NextResponse } from "next/server";
import { db, vercelTokens } from "@/drizzle/schema";
import { auth } from "@clerk/nextjs/server";
import { eq } from "drizzle-orm";
import { Vercel } from "@vercel/sdk";

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

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

if (!deployment) {
return new NextResponse("No deployment found", { status: 404 });
}

// Check for API keys and model names in Vercel environment
const vercel = new Vercel({
bearerToken: deployment.token,
});

// @ts-ignore
const { envs } = await vercel.projects.filterProjectEnvs({
idOrName: deployment.projectId,
});

// Find environment variables
const openaiKeyPresent = envs.some(env => env.key === "OPENAI_API_KEY");
const anthropicKeyPresent = envs.some(env => env.key === "ANTHROPIC_API_KEY");
const currentModelName = envs.find(env => env.key === "MODEL_NAME")?.value || 'gpt-4o';
const currentVisionModelName = envs.find(env => env.key === "VISION_MODEL_NAME")?.value || 'gpt-4o';

return NextResponse.json({
projectUrl: deployment.projectUrl,
deploymentUrl: deployment.deploymentUrl,
lastDeployment: deployment.lastDeployment,
modelName: currentModelName,
visionModelName: currentVisionModelName,
lastApiKeyUpdate: deployment.lastApiKeyUpdate,
openaiKeyPresent,
anthropicKeyPresent,
});
} catch (error) {
console.error("Error fetching deployment status:", error);
return NextResponse.json(
{ error: "Failed to fetch deployment status" },
{ status: 500 }
);
}
}
79 changes: 79 additions & 0 deletions web/app/api/deployment/update-key/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { NextResponse } from "next/server";
import { db, vercelTokens } from "@/drizzle/schema";
import { auth } from "@clerk/nextjs/server";
import { eq } from "drizzle-orm";
import { Vercel } from "@vercel/sdk";

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

const { provider, modelApiKey, modelName, soloApiKey } = await request.json();

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

if (!deployment) {
return new NextResponse("No deployment found", { status: 404 });
}

const vercel = new Vercel({
bearerToken: deployment.token,
});

// Prepare environment variables
const envVars = [
{
key: provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY',
value: modelApiKey,
type: "encrypted",
target: ["production"],
},
];

// Add SOLO_API_KEY if provided
if (soloApiKey) {
envVars.push({
key: 'SOLO_API_KEY',
value: soloApiKey,
type: "encrypted",
target: ["production"],
});
}

// Update environment variables using the correct method
await vercel.projects.createProjectEnv({
idOrName: deployment.projectId,
upsert: 'true',
// @ts-ignore
requestBody: envVars,
})



// Update database record
await db
.update(vercelTokens)
.set({
modelProvider: provider,
modelName,
lastApiKeyUpdate: new Date(),
updatedAt: new Date(),
})
.where(eq(vercelTokens.userId, userId));

return NextResponse.json({ success: true });
} catch (error) {
console.error("Error updating API key:", error);
return NextResponse.json(
{ error: "Failed to update API key" },
{ status: 500 }
);
}
}
29 changes: 20 additions & 9 deletions web/app/api/redeploy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,35 @@ export async function POST() {
return new NextResponse("Unauthorized", { status: 401 });
}

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

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

const vercel = new Vercel({
bearerToken: tokenRecord[0].token,
bearerToken: deployment.token,
});

const repo = "file-organizer-2000";
const org = "different-ai";
const ref = "master";

const deployment = await vercel.deployments.createDeployment({
// Create new deployment
const result = await vercel.deployments.createDeployment({
requestBody: {
name: `file-organizer-redeploy-${Date.now()}`,
target: "production",
project: tokenRecord[0].projectId,
project: deployment.projectId,
gitSource: {
type: "github",
repo: repo,
ref: ref,
org: org,
repo,
ref,
org,
},
projectSettings: {
framework: "nextjs",
Expand All @@ -49,9 +51,18 @@ export async function POST() {
},
});

// Update last deployment timestamp
await db
.update(vercelTokens)
.set({
lastDeployment: new Date(),
updatedAt: new Date(),
})
.where(eq(vercelTokens.userId, userId));

return NextResponse.json({
success: true,
deploymentUrl: deployment.url,
deploymentUrl: result.url,
});
} catch (error) {
console.error("Error in redeploy:", error);
Expand Down
Loading

0 comments on commit 4f39cc5

Please sign in to comment.