|
|
|
@@ -5,8 +5,8 @@ import { assertAgencyAdmin, getRouteAgencyContextOrThrow } from "@/lib/agency";
|
|
|
|
import { assertRateLimit } from "@/lib/api/rate-limit";
|
|
|
|
import { assertRateLimit } from "@/lib/api/rate-limit";
|
|
|
|
import { assertSameOrigin } from "@/lib/api/security";
|
|
|
|
import { assertSameOrigin } from "@/lib/api/security";
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
suspendPleskSubscription,
|
|
|
|
parseSubscriptionStatusDetailsFromInfo,
|
|
|
|
unsuspendPleskSubscription,
|
|
|
|
pleskCliCall,
|
|
|
|
} from "@/lib/plesk/client";
|
|
|
|
} from "@/lib/plesk/client";
|
|
|
|
import { createSupabaseRouteClient } from "@/lib/supabase/route";
|
|
|
|
import { createSupabaseRouteClient } from "@/lib/supabase/route";
|
|
|
|
|
|
|
|
|
|
|
|
@@ -23,7 +23,11 @@ export async function POST(
|
|
|
|
let context: Awaited<ReturnType<typeof getRouteAgencyContextOrThrow>> | null =
|
|
|
|
let context: Awaited<ReturnType<typeof getRouteAgencyContextOrThrow>> | null =
|
|
|
|
null;
|
|
|
|
null;
|
|
|
|
let domain: { id: string; subscription_id: string | null } | null = null;
|
|
|
|
let domain: { id: string; subscription_id: string | null } | null = null;
|
|
|
|
let subscription: { id: string; plesk_subscription_id: string } | null = null;
|
|
|
|
let subscription: {
|
|
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
|
|
plesk_subscription_id: string;
|
|
|
|
|
|
|
|
plesk_instance_id: string;
|
|
|
|
|
|
|
|
} | null = null;
|
|
|
|
let requestedAction: "suspend" | "unsuspend" | null = null;
|
|
|
|
let requestedAction: "suspend" | "unsuspend" | null = null;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
@@ -75,6 +79,7 @@ export async function POST(
|
|
|
|
subscription = {
|
|
|
|
subscription = {
|
|
|
|
id: String(subscriptionRow.id),
|
|
|
|
id: String(subscriptionRow.id),
|
|
|
|
plesk_subscription_id: String(subscriptionRow.plesk_subscription_id),
|
|
|
|
plesk_subscription_id: String(subscriptionRow.plesk_subscription_id),
|
|
|
|
|
|
|
|
plesk_instance_id: String(subscriptionRow.plesk_instance_id),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const { data: instance, error: instanceError } = await supabase
|
|
|
|
const { data: instance, error: instanceError } = await supabase
|
|
|
|
@@ -94,18 +99,100 @@ export async function POST(
|
|
|
|
encryptedSecret: instance.encrypted_secret,
|
|
|
|
encryptedSecret: instance.encrypted_secret,
|
|
|
|
} as const;
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
|
|
if (action === "suspend") {
|
|
|
|
const domainIdentifier = subscription.plesk_subscription_id;
|
|
|
|
await suspendPleskSubscription(
|
|
|
|
const siteArgs =
|
|
|
|
connection,
|
|
|
|
action === "suspend"
|
|
|
|
subscription.plesk_subscription_id,
|
|
|
|
? ["--suspend", domainIdentifier]
|
|
|
|
);
|
|
|
|
: ["--on", domainIdentifier];
|
|
|
|
} else {
|
|
|
|
|
|
|
|
await unsuspendPleskSubscription(
|
|
|
|
console.info("[domain_action] site CLI", {
|
|
|
|
connection,
|
|
|
|
action,
|
|
|
|
subscription.plesk_subscription_id,
|
|
|
|
command: "site",
|
|
|
|
|
|
|
|
args: siteArgs,
|
|
|
|
|
|
|
|
domain_id: domain.id,
|
|
|
|
|
|
|
|
subscription_id: subscription.id,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const siteResult = await pleskCliCall(connection, "site", siteArgs);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.info("[domain_action] site CLI result", {
|
|
|
|
|
|
|
|
code: siteResult.code,
|
|
|
|
|
|
|
|
stdout: siteResult.stdout,
|
|
|
|
|
|
|
|
stderr: siteResult.stderr,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const fallbackStatus = action === "suspend" ? "suspended" : "active";
|
|
|
|
|
|
|
|
let refreshedStatus = fallbackStatus;
|
|
|
|
|
|
|
|
let refreshedStatusRaw: string | null = null;
|
|
|
|
|
|
|
|
let refreshErrorMessage: string | null = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const infoResult = await pleskCliCall(connection, "subscription", [
|
|
|
|
|
|
|
|
"--info",
|
|
|
|
|
|
|
|
domainIdentifier,
|
|
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.info("[domain_action] subscription info result", {
|
|
|
|
|
|
|
|
code: infoResult.code,
|
|
|
|
|
|
|
|
stdout: infoResult.stdout,
|
|
|
|
|
|
|
|
stderr: infoResult.stderr,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const parsed = parseSubscriptionStatusDetailsFromInfo(
|
|
|
|
|
|
|
|
infoResult.stdout ?? "",
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
refreshedStatus = parsed.status;
|
|
|
|
|
|
|
|
refreshedStatusRaw = parsed.statusRaw;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
action === "unsuspend" &&
|
|
|
|
|
|
|
|
refreshedStatus === "suspended" &&
|
|
|
|
|
|
|
|
(refreshedStatusRaw ?? "").toLowerCase().includes("subscriber")
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
throw new Error(
|
|
|
|
|
|
|
|
"Domain remains suspended because its subscriber/customer account is suspended.",
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (refreshError) {
|
|
|
|
|
|
|
|
refreshErrorMessage =
|
|
|
|
|
|
|
|
refreshError instanceof Error
|
|
|
|
|
|
|
|
? refreshError.message.slice(0, 1000)
|
|
|
|
|
|
|
|
: "subscription_status_refresh_failed";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await supabase.from("actions_log").insert({
|
|
|
|
|
|
|
|
agency_id: context.agencyId,
|
|
|
|
|
|
|
|
actor_user_id: context.userId,
|
|
|
|
|
|
|
|
target_type: "plesk_subscription",
|
|
|
|
|
|
|
|
target_id: subscription.id,
|
|
|
|
|
|
|
|
action: "subscription_status_refresh_failed",
|
|
|
|
|
|
|
|
status: "failed",
|
|
|
|
|
|
|
|
error_message: refreshErrorMessage,
|
|
|
|
|
|
|
|
metadata: {
|
|
|
|
|
|
|
|
subscription_id: subscription.id,
|
|
|
|
|
|
|
|
plesk_subscription_id: subscription.plesk_subscription_id,
|
|
|
|
|
|
|
|
plesk_instance_id: subscription.plesk_instance_id,
|
|
|
|
|
|
|
|
source: "domain_action",
|
|
|
|
|
|
|
|
domain_id: domain.id,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await supabase
|
|
|
|
|
|
|
|
.from("plesk_subscriptions")
|
|
|
|
|
|
|
|
.update({
|
|
|
|
|
|
|
|
status: refreshedStatus,
|
|
|
|
|
|
|
|
status_raw: refreshedStatusRaw,
|
|
|
|
|
|
|
|
last_status_sync_at: new Date().toISOString(),
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.eq("id", subscription.id)
|
|
|
|
|
|
|
|
.eq("agency_id", context.agencyId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await supabase
|
|
|
|
|
|
|
|
.from("plesk_domains")
|
|
|
|
|
|
|
|
.update({ status: refreshedStatus })
|
|
|
|
|
|
|
|
.eq("agency_id", context.agencyId)
|
|
|
|
|
|
|
|
.eq("subscription_id", subscription.id);
|
|
|
|
|
|
|
|
|
|
|
|
await supabase.from("actions_log").insert({
|
|
|
|
await supabase.from("actions_log").insert({
|
|
|
|
agency_id: context.agencyId,
|
|
|
|
agency_id: context.agencyId,
|
|
|
|
actor_user_id: context.userId,
|
|
|
|
actor_user_id: context.userId,
|
|
|
|
@@ -117,6 +204,8 @@ export async function POST(
|
|
|
|
domain_id: domain.id,
|
|
|
|
domain_id: domain.id,
|
|
|
|
subscription_id: subscription.id,
|
|
|
|
subscription_id: subscription.id,
|
|
|
|
plesk_subscription_id: subscription.plesk_subscription_id,
|
|
|
|
plesk_subscription_id: subscription.plesk_subscription_id,
|
|
|
|
|
|
|
|
refreshed_status: refreshedStatus,
|
|
|
|
|
|
|
|
refresh_error: refreshErrorMessage,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|