"use server"; import { revalidatePath } from "next/cache"; import ecsClient, { ec2Client } from "./ecs"; import { CreateServiceCommand, DescribeClustersCommand, DescribeServicesCommand, DescribeTasksCommand, ListTasksCommand, } from "@aws-sdk/client-ecs"; import { DescribeNetworkInterfacesCommand } from "@aws-sdk/client-ec2"; export async function createSandbox(body: { type: string; name: string; userId: string; visibility: string; }) { const res = await fetch( "https://database.ishaan1013.workers.dev/api/sandbox", { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), } ); return await res.text(); } export async function updateSandbox(body: { id: string; name?: string; visibility?: "public" | "private"; }) { await fetch("https://database.ishaan1013.workers.dev/api/sandbox", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), }); revalidatePath("/dashboard"); } export async function deleteSandbox(id: string) { await fetch(`https://database.ishaan1013.workers.dev/api/sandbox?id=${id}`, { method: "DELETE", }); revalidatePath("/dashboard"); } export async function shareSandbox(sandboxId: string, email: string) { const res = await fetch( "https://database.ishaan1013.workers.dev/api/sandbox/share", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ sandboxId, email }), } ); const text = await res.text(); if (res.status !== 200) { return { success: false, message: text }; } revalidatePath(`/code/${sandboxId}`); return { success: true, message: "Shared successfully." }; } export async function unshareSandbox(sandboxId: string, userId: string) { await fetch("https://database.ishaan1013.workers.dev/api/sandbox/share", { method: "DELETE", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ sandboxId, userId }), }); revalidatePath(`/code/${sandboxId}`); } export async function describeService(serviceName: string) { const command = new DescribeServicesCommand({ cluster: process.env.NEXT_PUBLIC_AWS_ECS_CLUSTER!, services: [serviceName], }); return await ecsClient.send(command); } export async function getTaskIp(serviceName: string) { const listCommand = new ListTasksCommand({ cluster: process.env.NEXT_PUBLIC_AWS_ECS_CLUSTER!, serviceName, }); const listResponse = await ecsClient.send(listCommand); const taskArns = listResponse.taskArns; const describeCommand = new DescribeTasksCommand({ cluster: process.env.NEXT_PUBLIC_AWS_ECS_CLUSTER!, tasks: taskArns, }); const describeResponse = await ecsClient.send(describeCommand); const tasks = describeResponse.tasks; const taskAttachment = tasks?.[0].attachments?.[0].details; if (!taskAttachment) { throw new Error("Task attachment not found"); } const eni = taskAttachment.find( (detail) => detail.name === "networkInterfaceId" )?.value; if (!eni) { throw new Error("Network interface not found"); } const describeNetworkInterfacesCommand = new DescribeNetworkInterfacesCommand( { NetworkInterfaceIds: [eni], } ); const describeNetworkInterfacesResponse = await ec2Client.send( describeNetworkInterfacesCommand ); const ip = describeNetworkInterfacesResponse.NetworkInterfaces?.[0].Association ?.PublicIp; if (!ip) { throw new Error("Public IP not found"); } return ip; } async function doesServiceExist(serviceName: string) { const response = await describeService(serviceName); const activeServices = response.services?.filter( (service) => service.status === "ACTIVE" ); console.log("activeServices: ", activeServices); return activeServices?.length === 1; } async function countServices() { const command = new DescribeClustersCommand({ clusters: [process.env.NEXT_PUBLIC_AWS_ECS_CLUSTER!], }); const response = await ecsClient.send(command); return response.clusters?.[0].activeServicesCount!; } export async function startServer( serviceName: string ): Promise<{ success: boolean; message: string }> { const serviceExists = await doesServiceExist(serviceName); if (serviceExists) { return { success: true, message: "" }; } const activeServices = await countServices(); if (activeServices >= 100) { return { success: false, message: "Too many servers are running! Please try again later or contact @ishaandey_ on Twitter/X.", }; } const command = new CreateServiceCommand({ cluster: process.env.NEXT_PUBLIC_AWS_ECS_CLUSTER!, serviceName, taskDefinition: "Sandbox1", desiredCount: 1, launchType: "FARGATE", networkConfiguration: { awsvpcConfiguration: { securityGroups: [process.env.AWS_ECS_SECURITY_GROUP!], subnets: [ "subnet-06d04f2a6ebb1710c", "subnet-097c000f157c26a78", "subnet-00f931ecbabaf87dd", "subnet-0adcb82d77db9f263", "subnet-0c6874150d8e63a7c", "subnet-0b76f9ee3fe20660d", ], assignPublicIp: "ENABLED", }, }, }); try { const response = await ecsClient.send(command); console.log("started server:", response.service?.serviceName); return { success: true, message: "" }; // store in workers kv: // { // userId: { // sandboxId, // serviceName, // startedAt, // } // } } catch (error: any) { // console.error("Error starting server:", error.message); return { success: false, message: `Error starting server: ${error.message}. Try again in a minute, or contact @ishaandey_ on Twitter/X if it still doesn't work.`, }; } }