mywork / src /lib /actions /deployment.ts
DeeCeeXxx's picture
Upload 114 files
e9d5b7d verified
"use server";
import type { DeploymentFormInput } from "@/lib/schemas";
import type { Deployment, DeploymentStatus } from "@/lib/types";
import { analyzeDeploymentLogs as analyzeLogsFlow, type AnalyzeDeploymentLogsInput } from "@/ai/flows/analyze-deployment-logs";
import { getDb } from "@/lib/mongodb";
import { ObjectId } from 'mongodb';
import { getLoggedInUser } from "./auth";
import { getPlatformApiKey } from "./admin";
import { revalidatePath } from "next/cache";
const defaultGithubRepoUrl = "https://github.com/DavidCyrilTech/Anita-V4";
const DEPLOYMENT_COST = 10;
// GLOBAL_DEPLOYMENT_LIMIT removed
const HEROKU_API_BASE_URL = "https://api.heroku.com";
const HEROKU_STACK = "heroku-22";
async function getHerokuApiKey(): Promise<string | null> {
const result = await getPlatformApiKey();
if (result.success && result.apiKey && result.apiKey.trim() !== "") {
return result.apiKey;
}
console.warn("Heroku API Key not found or is empty in database settings via Admin Panel. Deployments will fail.");
return null;
}
async function herokuApiCall(
endpoint: string,
method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
apiKey: string,
body?: any
): Promise<any> {
const headers = {
'Accept': 'application/vnd.heroku+json; version=3',
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
};
const options: RequestInit = {
method,
headers,
};
if (body) {
options.body = JSON.stringify(body);
}
try {
const response = await fetch(`${HEROKU_API_BASE_URL}${endpoint}`, options);
if (!response.ok) {
if (method === 'DELETE' && response.status === 404) {
return { status: 404, message: "Resource not found on Heroku (already deleted or never existed)." };
}
const errorBody = await response.json().catch(() => ({ message: response.statusText }));
console.error(`Heroku API Error (${method} ${endpoint}): ${response.status}`, errorBody);
throw new Error(`Heroku API Error: ${errorBody.message || response.statusText} (Status: ${response.status})`);
}
if (response.status === 204 || response.headers.get("content-length") === "0") {
return null;
}
return response.json();
} catch (error) {
console.error(`Network or parsing error during Heroku API call (${method} ${endpoint}):`, error);
throw error;
}
}
export async function createNewDeployment(data: DeploymentFormInput): Promise<{ success: boolean; message: string; deployment?: Deployment }> {
const user = await getLoggedInUser();
if (!user) {
return { success: false, message: "You must be logged in to create a deployment." };
}
const herokuApiKey = await getHerokuApiKey();
if (!herokuApiKey) {
return { success: false, message: "Platform API Key is not configured. Deployment cannot proceed." };
}
try {
const db = await getDb();
const deploymentsCollection = db.collection<Deployment>("deployments");
const usersCollection = db.collection("users");
// Removed GLOBAL_DEPLOYMENT_LIMIT check here
if (user.role !== 'admin') {
const freshUser = await usersCollection.findOne({ _id: user._id });
if (!freshUser || freshUser.coins < DEPLOYMENT_COST) {
return { success: false, message: `Insufficient coins. You need ${DEPLOYMENT_COST} coins. You have ${freshUser?.coins || 0}.` };
}
}
const appNameInput = data.PLATFORM_APP_NAME || `anita-bot-${user.name.toLowerCase().replace(/[^a-z0-9]/g, '').slice(0,8)}-${new ObjectId().toString().slice(-4)}`;
const herokuAppName = appNameInput.toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 30);
const appNameProvidedByUser = !!data.PLATFORM_APP_NAME;
const existingDeployment = await deploymentsCollection.findOne({ appName: herokuAppName });
if (existingDeployment) {
return { success: false, message: `A deployment with app name '${herokuAppName}' already exists in our records. Please choose a different name or let one be auto-generated.` };
}
const now = new Date();
const initialLogs: string[] = [
`[SYSTEM] Info: ${now.toISOString()} - Deployment creation initiated by user ${user.email} for Heroku app: ${herokuAppName}.`,
`[SYSTEM] Info: ${now.toISOString()} - Target GitHub Repo: ${defaultGithubRepoUrl}`
];
let herokuApp;
try {
initialLogs.push(`[SYSTEM] Info: ${now.toISOString()} - Attempting to create Heroku app '${herokuAppName}'...`);
herokuApp = await herokuApiCall('/apps', 'POST', herokuApiKey, { name: herokuAppName, region: 'us', stack: HEROKU_STACK });
initialLogs.push(`[SYSTEM] Success: ${new Date().toISOString()} - Heroku app '${herokuAppName}' (ID: ${herokuApp.id}) created successfully. Web URL: ${herokuApp.web_url}`);
} catch (error: any) {
if (error.message && error.message.includes("Name") && error.message.includes("is already taken") && error.message.includes("(Status: 422)")) {
initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Heroku app name '${herokuAppName}' is already taken.`);
let userMessage = `The app name '${herokuAppName}' is already taken on Heroku. `;
if (appNameProvidedByUser) {
userMessage += "Please choose a different name for your app and try again.";
} else {
userMessage += "This name was auto-generated. Please try deploying again, as a new name will be generated.";
}
return { success: false, message: userMessage };
}
initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Failed to create Heroku app: ${error.message}`);
const tempFailDeployment: Omit<Deployment, '_id'> = {
id: herokuAppName,
userId: user._id,
appName: herokuAppName,
status: 'failed',
createdAt: now.toISOString(),
region: 'us',
logs: initialLogs,
envVariables: { ...data },
githubRepoUrl: defaultGithubRepoUrl,
};
await deploymentsCollection.insertOne(tempFailDeployment);
return { success: false, message: `Failed to create Heroku app: ${error.message}` };
}
initialLogs.push(`[SYSTEM] Info: ${new Date().toISOString()} - Setting environment variables for '${herokuAppName}'...`);
const envVarsToSet = { ...data };
delete envVarsToSet.PLATFORM_APP_NAME;
const herokuConfigVars = { ...envVarsToSet, GITHUB_REPO_URL: defaultGithubRepoUrl };
try {
await herokuApiCall(`/apps/${herokuApp.id}/config-vars`, 'PATCH', herokuApiKey, herokuConfigVars);
initialLogs.push(`[SYSTEM] Success: ${new Date().toISOString()} - Environment variables set successfully.`);
} catch (error: any) {
initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Failed to set environment variables: ${error.message}. Cleaning up Heroku app.`);
await herokuApiCall(`/apps/${herokuApp.id}`, 'DELETE', herokuApiKey).catch(delErr => console.error("Cleanup error after failing to set config vars:", delErr));
return { success: false, message: `Failed to set Heroku environment variables: ${error.message}` };
}
initialLogs.push(`[SYSTEM] Info: ${new Date().toISOString()} - Triggering build from GitHub repository '${defaultGithubRepoUrl}'...`);
let build;
try {
build = await herokuApiCall(`/apps/${herokuApp.id}/builds`, 'POST', herokuApiKey, {
source_blob: {
url: `${defaultGithubRepoUrl}/tarball/main/`,
version: 'main'
},
});
initialLogs.push(`[SYSTEM] Success: ${new Date().toISOString()} - Build initiated successfully. Build ID: ${build.id}, Status: ${build.status}.`);
} catch (error: any) {
initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Failed to trigger build: ${error.message}. Cleaning up Heroku app.`);
await herokuApiCall(`/apps/${herokuApp.id}`, 'DELETE', herokuApiKey).catch(delErr => console.error("Cleanup error after failing to trigger build:", delErr));
return { success: false, message: `Failed to trigger Heroku build: ${error.message}` };
}
const newDeploymentData: Omit<Deployment, '_id'> = {
id: herokuApp.id,
userId: user._id,
appName: herokuApp.name,
status: build.status === 'succeeded' ? 'succeeded' : 'deploying',
createdAt: now.toISOString(),
lastDeployedAt: build.status === 'succeeded' ? new Date(build.updated_at).toISOString() : undefined,
region: herokuApp.region.name,
url: herokuApp.web_url,
logs: initialLogs,
envVariables: { ...data },
githubRepoUrl: defaultGithubRepoUrl,
};
const result = await deploymentsCollection.insertOne(newDeploymentData);
if (!result.insertedId) {
initialLogs.push(`[SYSTEM] Error: ${new Date().toISOString()} - Failed to save deployment to local database. Critical error. Heroku app '${herokuApp.name}' might be orphaned.`);
await herokuApiCall(`/apps/${herokuApp.id}`, 'DELETE', herokuApiKey).catch(delErr => console.error("Critical: Cleanup error for orphaned Heroku app:", delErr));
return { success: false, message: "Critical: Failed to save deployment to local database after Heroku setup." };
}
if (user.role !== 'admin') {
await usersCollection.updateOne(
{ _id: user._id },
{ $inc: { coins: -DEPLOYMENT_COST } }
);
}
revalidatePath("/dashboard");
revalidatePath(`/dashboard/deployments/${newDeploymentData.id}`);
return {
success: true,
message: `Heroku deployment for '${newDeploymentData.appName}' initiated! ${user.role !== 'admin' ? `${DEPLOYMENT_COST} coins deducted.` : ''} Build status: ${build.status}. Check deployment details for updates.`,
deployment: { ...newDeploymentData, _id: result.insertedId.toString() }
};
} catch (error: any) {
console.error("Error creating new Heroku deployment:", error);
const message = error.message || "An unexpected error occurred during Heroku deployment.";
return { success: false, message };
}
}
export async function getDeployments(): Promise<Deployment[]> {
const user = await getLoggedInUser();
if (!user) return [];
try {
const db = await getDb();
const query = user.role === 'admin' ? {} : { userId: user._id };
const deployments = await db.collection<Deployment>("deployments")
.find(query)
.sort({ createdAt: -1 })
.toArray();
return deployments.map(d => ({ ...d, _id: d._id?.toString() }));
} catch (error) {
console.error("Error fetching deployments:", error);
return [];
}
}
export async function getDeploymentById(id: string): Promise<Deployment | null> {
const user = await getLoggedInUser();
if (!user) return null;
try {
const db = await getDb();
const deployment = await db.collection<Deployment>("deployments").findOne({ id: id });
if (!deployment) return null;
if (deployment.userId !== user._id && user.role !== 'admin') return null;
if (deployment.status === 'deploying' || deployment.status === 'pending') {
const herokuApiKey = await getHerokuApiKey();
if (herokuApiKey) {
try {
const formation = await herokuApiCall(`/apps/${deployment.id}/formation/web`, 'GET', herokuApiKey);
let appStatusBasedOnDynos: DeploymentStatus = deployment.status;
if (formation && formation.quantity > 0) {
if(deployment.status === 'deploying') appStatusBasedOnDynos = 'succeeded';
} else if (formation && formation.quantity === 0 && deployment.status !== 'stopped') {
appStatusBasedOnDynos = 'stopped';
}
const builds = await herokuApiCall(`/apps/${deployment.id}/builds`, 'GET', herokuApiKey);
let newStatusFromBuild = deployment.status;
let lastDeployedAtFromBuild = deployment.lastDeployedAt;
if (builds && builds.length > 0) {
const latestBuild = builds.sort((a:any, b:any) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())[0];
newStatusFromBuild = latestBuild.status === 'succeeded' ? 'succeeded' : latestBuild.status === 'failed' ? 'failed' : 'deploying';
lastDeployedAtFromBuild = new Date(latestBuild.updated_at).toISOString();
}
let finalNewStatus = deployment.status;
if (newStatusFromBuild === 'succeeded' || newStatusFromBuild === 'failed') {
finalNewStatus = newStatusFromBuild;
} else if (appStatusBasedOnDynos !== deployment.status) {
finalNewStatus = appStatusBasedOnDynos;
}
if (finalNewStatus !== deployment.status || (finalNewStatus === 'succeeded' && lastDeployedAtFromBuild !== deployment.lastDeployedAt)) {
deployment.status = finalNewStatus;
deployment.lastDeployedAt = finalNewStatus === 'succeeded' ? lastDeployedAtFromBuild : deployment.lastDeployedAt;
const statusUpdateLog = `[SYSTEM] Info: ${new Date().toISOString()} - Heroku status sync. App status: ${finalNewStatus}. Latest build status: ${newStatusFromBuild}.`;
const updatedLogs = [...(deployment.logs || []), statusUpdateLog].slice(-500);
await db.collection<Deployment>("deployments").updateOne(
{ id: deployment.id },
{ $set: { status: finalNewStatus, lastDeployedAt: deployment.lastDeployedAt, logs: updatedLogs } }
);
deployment.logs = updatedLogs;
revalidatePath(`/dashboard/deployments/${deployment.id}`);
}
} catch (herokuError: any) {
const errorLog = `[SYSTEM] Warning: ${new Date().toISOString()} - Could not fetch live status from Heroku: ${herokuError.message}`;
const updatedLogs = [...(deployment.logs || []), errorLog].slice(-500);
await db.collection<Deployment>("deployments").updateOne(
{ id: deployment.id },
{ $set: { logs: updatedLogs } }
);
deployment.logs = updatedLogs;
}
}
}
return { ...deployment, _id: deployment._id?.toString() };
} catch (error) {
console.error(`Error fetching deployment by ID '${id}':`, error);
return null;
}
}
export async function getDeploymentLogs(deploymentId: string): Promise<string[]> {
const user = await getLoggedInUser();
if (!user) return [`[SYSTEM] Error: Unauthorized to fetch logs.`];
const herokuApiKey = await getHerokuApiKey();
if (!herokuApiKey) return [`[SYSTEM] Error: Heroku API Key not configured. Cannot fetch live logs.`];
const db = await getDb();
const deploymentsCollection = db.collection<Deployment>("deployments");
const deployment = await deploymentsCollection.findOne({ id: deploymentId });
if (!deployment) return [`[SYSTEM] Info: No deployment found with ID ${deploymentId}.`];
if (deployment.userId !== user._id && user.role !== 'admin') {
return [`[SYSTEM] Error: Unauthorized to fetch logs for this deployment.`];
}
const timestamp = new Date().toISOString();
let fetchedHerokuLogs: string[] = [];
try {
const logSession = await herokuApiCall(
`/apps/${deployment.id}/log-sessions`,
'POST',
herokuApiKey,
{ lines: 200, source: 'app', tail: false }
);
if (logSession && logSession.logplex_url) {
const logStreamResponse = await fetch(logSession.logplex_url);
if (logStreamResponse.ok) {
const rawLogs = await logStreamResponse.text();
fetchedHerokuLogs = rawLogs.split('\\n').filter(line => line.trim() !== '');
if (fetchedHerokuLogs.length > 0) {
fetchedHerokuLogs.unshift(`[SYSTEM] Info: ${timestamp} - Successfully fetched ${fetchedHerokuLogs.length} recent log lines from Heroku.`);
} else {
fetchedHerokuLogs.push(`[SYSTEM] Info: ${timestamp} - No recent logs returned from Heroku for this app.`);
}
const currentDbLogs = deployment.logs || [];
const combinedLogs = [...currentDbLogs, ...fetchedHerokuLogs].slice(-500);
await deploymentsCollection.updateOne(
{ id: deployment.id },
{ $set: { logs: combinedLogs } }
);
revalidatePath(`/dashboard/deployments/${deployment.id}`);
return combinedLogs;
} else {
const errorMsg = `[SYSTEM] Error: ${timestamp} - Failed to fetch logs from Heroku logplex_url. Status: ${logStreamResponse.status}`;
fetchedHerokuLogs = [errorMsg];
}
} else {
const errorMsg = `[SYSTEM] Error: ${timestamp} - Failed to create Heroku log session. Response: ${JSON.stringify(logSession)}`;
fetchedHerokuLogs = [errorMsg];
}
} catch (error: any) {
console.error(`Error fetching live logs from Heroku for '${deployment.appName}':`, error);
const errorMsg = `[SYSTEM] Error: ${timestamp} - Exception during Heroku log fetch: ${error.message}`;
fetchedHerokuLogs = [errorMsg];
}
const currentDbLogs = deployment.logs || [];
const logsToStore = [...currentDbLogs, ...fetchedHerokuLogs].slice(-500);
await deploymentsCollection.updateOne(
{ id: deployment.id },
{ $set: { logs: logsToStore } }
);
revalidatePath(`/dashboard/deployments/${deployment.id}`);
return logsToStore;
}
export async function controlDeployment(deploymentId: string, action: "start" | "stop" | "restart"): Promise<{ success: boolean; message: string; newStatus?: DeploymentStatus }> {
const user = await getLoggedInUser();
if (!user) return { success: false, message: "Unauthorized." };
const herokuApiKey = await getHerokuApiKey();
if (!herokuApiKey) return { success: false, message: "Heroku API Key is not configured." };
const db = await getDb();
const deploymentsCollection = db.collection<Deployment>("deployments");
const deployment = await deploymentsCollection.findOne({ id: deploymentId });
if (!deployment) return { success: false, message: "Deployment not found." };
if (deployment.userId !== user._id && user.role !== 'admin') {
return { success: false, message: "Unauthorized to control this deployment." };
}
const herokuAppIdOrName = deployment.id;
let newDbStatus: DeploymentStatus = deployment.status;
const timestamp = new Date().toISOString();
let logMessage = `[SYSTEM] Info: ${timestamp} - User ${user.email} requested Heroku action '${action}' on app '${deployment.appName}'.`;
let herokuSuccess = false;
try {
switch (action) {
case "start":
logMessage += ` Attempting to scale web dynos to 1.`;
await herokuApiCall(`/apps/${herokuAppIdOrName}/formation/web`, 'PATCH', herokuApiKey, { quantity: 1 });
newDbStatus = 'deploying';
logMessage += ` Heroku accepted scale command. App is starting. Status set to 'deploying'.`;
herokuSuccess = true;
break;
case "stop":
logMessage += ` Attempting to scale web dynos to 0.`;
await herokuApiCall(`/apps/${herokuAppIdOrName}/formation/web`, 'PATCH', herokuApiKey, { quantity: 0 });
newDbStatus = 'stopped';
logMessage += ` Heroku accepted scale command. App should be stopping. Status set to 'stopped'.`;
herokuSuccess = true;
break;
case "restart":
logMessage += ` Attempting to restart all dynos.`;
await herokuApiCall(`/apps/${herokuAppIdOrName}/dynos`, 'DELETE', herokuApiKey);
newDbStatus = 'deploying';
logMessage += ` Heroku accepted restart command. App is restarting. Status set to 'deploying'.`;
herokuSuccess = true;
break;
}
if (herokuSuccess) {
const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
await deploymentsCollection.updateOne(
{ id: deploymentId },
{ $set: { status: newDbStatus, logs: updatedLogs, lastDeployedAt: new Date().toISOString() } }
);
revalidatePath(`/dashboard/deployments/${deploymentId}`);
return { success: true, message: `Heroku action '${action}' on '${deployment.appName}' initiated. New status: ${newDbStatus}.`, newStatus: newDbStatus };
} else {
logMessage += ` Failed to execute Heroku command for '${action}'.`;
const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
await deploymentsCollection.updateOne({ id: deploymentId }, { $set: {logs: updatedLogs }});
return { success: false, message: `Failed to execute Heroku command for '${action}'.` };
}
} catch (error: any) {
logMessage += ` Error during Heroku action '${action}': ${error.message}.`;
const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
const currentDeploymentRecord = await deploymentsCollection.findOne({ id: deploymentId });
if (currentDeploymentRecord) {
await deploymentsCollection.updateOne({ id: deploymentId }, { $set: { logs: updatedLogs } });
}
console.error(`Error controlling Heroku deployment '${deployment.appName}':`, error);
return { success: false, message: `Heroku API operation failed: ${error.message}` };
}
}
export async function deleteDeployment(deploymentId: string): Promise<{ success: boolean; message: string }> {
const user = await getLoggedInUser();
if (!user) return { success: false, message: "Unauthorized. Please log in." };
const herokuApiKey = await getHerokuApiKey();
if (!herokuApiKey) return { success: false, message: "Heroku API Key is not configured. Cannot delete deployment." };
const db = await getDb();
const deploymentsCollection = db.collection<Deployment>("deployments");
const deployment = await deploymentsCollection.findOne({ id: deploymentId });
if (!deployment) return { success: false, message: "Deployment not found in our records." };
if (deployment.userId !== user._id && user.role !== 'admin') {
return { success: false, message: "Unauthorized. You can only delete your own deployments." };
}
const timestamp = new Date().toISOString();
const initialDbLogs = deployment.logs || [];
let logMessage = `[SYSTEM] Info: ${timestamp} - User ${user.email} initiated deletion for deployment '${deployment.appName}' (ID: ${deployment.id}).`;
let herokuAppDeleted = false;
try {
logMessage += ` Attempting to delete Heroku app '${deployment.appName}'.`;
await herokuApiCall(`/apps/${deployment.id}`, 'DELETE', herokuApiKey);
logMessage += ` Heroku app '${deployment.appName}' delete command accepted by Heroku.`;
herokuAppDeleted = true;
} catch (error: any) {
if (error.message && error.message.includes("(Status: 404)") || (typeof error === 'object' && error !== null && 'status' in error && error.status === 404)) {
logMessage += ` Heroku app '${deployment.appName}' not found on Heroku (Status 404). Assuming already deleted or never fully created. Proceeding with local cleanup.`;
herokuAppDeleted = true;
} else {
logMessage += ` Error deleting Heroku app '${deployment.appName}': ${error.message}. Local record will NOT be deleted.`;
const updatedLogs = [...initialDbLogs, logMessage].slice(-500);
const currentDeploymentRecord = await deploymentsCollection.findOne({ id: deploymentId });
if (currentDeploymentRecord) {
await deploymentsCollection.updateOne({ id: deploymentId }, { $set: { logs: updatedLogs } });
revalidatePath(`/dashboard/deployments/${deploymentId}`);
}
return { success: false, message: `Failed to delete Heroku app: ${error.message}.` };
}
}
if (herokuAppDeleted) {
try {
const deleteResult = await deploymentsCollection.deleteOne({ id: deployment.id });
if (deleteResult.deletedCount === 0) {
logMessage += ` Warning: Heroku app was targeted for deletion, but local record for '${deployment.appName}' not found or already deleted.`;
console.warn(logMessage);
} else {
logMessage += ` Local database record for deployment '${deployment.appName}' deleted successfully.`;
}
revalidatePath("/dashboard");
revalidatePath(`/dashboard/deployments`);
return { success: true, message: `Deployment '${deployment.appName}' and associated Heroku app have been successfully deleted (or confirmed deleted).` };
} catch (dbError: any) {
logMessage += ` Critical Error: Heroku app '${deployment.appName}' was deleted (or confirmed deleted), but failed to delete local database record: ${dbError.message}. Manual cleanup may be required.`;
console.error(logMessage);
return { success: false, message: `Heroku app deleted, but failed to clean up local record: ${dbError.message}` };
}
}
return { success: false, message: "An unknown error occurred during the deletion process." };
}
export async function analyzeDeploymentLogs(logs: string): Promise<{ success: boolean; analysis?: string; error?: string }> {
if (!logs.trim()) {
return { success: false, error: "Log content cannot be empty." };
}
try {
const input: AnalyzeDeploymentLogsInput = { deploymentLogs: logs };
const result = await analyzeLogsFlow(input);
return { success: true, analysis: result.analysisResult };
} catch (error) {
console.error("AI Log Analysis Error:", error);
return { success: false, error: "Failed to analyze logs. Please try again." };
}
}
export async function updateDeploymentEnvVariables(
deploymentId: string,
newEnvVars: DeploymentFormInput
): Promise<{ success: boolean; message: string }> {
const user = await getLoggedInUser();
if (!user) {
return { success: false, message: "Unauthorized. Please log in." };
}
const herokuApiKey = await getHerokuApiKey();
if (!herokuApiKey) {
return { success: false, message: "Heroku API Key is not configured." };
}
const db = await getDb();
const deploymentsCollection = db.collection<Deployment>("deployments");
const deployment = await deploymentsCollection.findOne({ id: deploymentId });
if (!deployment) {
return { success: false, message: "Deployment not found." };
}
if (deployment.userId !== user._id && user.role !== 'admin') {
return { success: false, message: "Unauthorized to modify this deployment." };
}
const timestamp = new Date().toISOString();
let logMessage = `[SYSTEM] Info: ${timestamp} - User ${user.email} initiated environment variable update for '${deployment.appName}'.`;
try {
const herokuConfigVars = { ...newEnvVars };
if ('PLATFORM_APP_NAME' in herokuConfigVars) {
delete (herokuConfigVars as any).PLATFORM_APP_NAME;
}
if (!(herokuConfigVars as any).GITHUB_REPO_URL) {
(herokuConfigVars as any).GITHUB_REPO_URL = deployment.githubRepoUrl || defaultGithubRepoUrl;
}
logMessage += ` Attempting to update config vars on Heroku.`;
await herokuApiCall(`/apps/${deployment.id}/config-vars`, 'PATCH', herokuApiKey, herokuConfigVars);
logMessage += ` Heroku config vars updated successfully.`;
logMessage += ` Attempting to restart Heroku app '${deployment.appName}' for changes to take effect.`;
await herokuApiCall(`/apps/${deployment.id}/dynos`, 'DELETE', herokuApiKey);
logMessage += ` Heroku app restart command accepted. Status set to 'deploying'.`;
const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
await deploymentsCollection.updateOne(
{ id: deployment.id },
{ $set: { envVariables: newEnvVars, logs: updatedLogs, status: 'deploying', lastDeployedAt: new Date().toISOString() } }
);
revalidatePath(`/dashboard/deployments/${deployment.id}`);
return { success: true, message: "Environment variables updated and app restart initiated. Status is 'deploying'." };
} catch (error: any) {
logMessage += ` Error during environment variable update or restart: ${error.message}.`;
const updatedLogs = [...(deployment.logs || []), logMessage].slice(-500);
const currentDeploymentRecord = await deploymentsCollection.findOne({ id: deploymentId });
if (currentDeploymentRecord) {
await deploymentsCollection.updateOne({ id: deployment.id }, { $set: { logs: updatedLogs } });
}
console.error(`Error updating env variables for '${deployment.appName}':`, error);
return { success: false, message: `Failed to update environment variables: ${error.message}` };
}
}