Compare commits

...

4 Commits

2 changed files with 89 additions and 13 deletions

View File

@@ -5,6 +5,8 @@ import { assertAgencyAdmin, getRouteAgencyContextOrThrow } from "@/lib/agency";
import { assertRateLimit } from "@/lib/api/rate-limit";
import { assertSameOrigin } from "@/lib/api/security";
import {
parseSubscriptionStatusDetailsFromInfo,
pleskCliCall,
suspendPleskSubscription,
unsuspendPleskSubscription,
} from "@/lib/plesk/client";
@@ -12,6 +14,7 @@ import { createSupabaseRouteClient } from "@/lib/supabase/route";
const actionSchema = z.object({
action: z.enum(["suspend", "unsuspend"]),
subscription_id: z.string().uuid().optional(),
});
export async function POST(
@@ -22,7 +25,11 @@ export async function POST(
let context: Awaited<ReturnType<typeof getRouteAgencyContextOrThrow>> | 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;
try {
@@ -32,7 +39,8 @@ export async function POST(
assertAgencyAdmin(context);
assertRateLimit(`plesk-domain-action:${context.userId}`, 10, 60_000);
const { action } = actionSchema.parse(await req.json());
const payload = actionSchema.parse(await req.json());
const { action } = payload;
requestedAction = action;
const { data: domainRow, error: domainError } = await supabase
@@ -46,21 +54,23 @@ export async function POST(
throw new Error("Domain not found");
}
const resolvedSubscriptionId =
payload.subscription_id ??
(domainRow.subscription_id ? String(domainRow.subscription_id) : null);
domain = {
id: String(domainRow.id),
subscription_id: domainRow.subscription_id
? String(domainRow.subscription_id)
: null,
subscription_id: resolvedSubscriptionId,
};
if (!domain.subscription_id) {
if (!resolvedSubscriptionId) {
throw new Error("Domain is not linked to a subscription");
}
const { data: subscriptionRow, error: subscriptionError } = await supabase
.from("plesk_subscriptions")
.select("id, plesk_subscription_id, plesk_instance_id")
.eq("id", domain.subscription_id)
.eq("id", resolvedSubscriptionId)
.eq("agency_id", context.agencyId)
.single();
@@ -71,6 +81,7 @@ export async function POST(
subscription = {
id: String(subscriptionRow.id),
plesk_subscription_id: String(subscriptionRow.plesk_subscription_id),
plesk_instance_id: String(subscriptionRow.plesk_instance_id),
};
const { data: instance, error: instanceError } = await supabase
@@ -102,6 +113,61 @@ export async function POST(
);
}
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",
subscription.plesk_subscription_id,
]);
const parsed = parseSubscriptionStatusDetailsFromInfo(
infoResult.stdout ?? "",
);
refreshedStatus = parsed.status;
refreshedStatusRaw = parsed.statusRaw;
} 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({
agency_id: context.agencyId,
actor_user_id: context.userId,
@@ -113,6 +179,8 @@ export async function POST(
domain_id: domain.id,
subscription_id: subscription.id,
plesk_subscription_id: subscription.plesk_subscription_id,
refreshed_status: refreshedStatus,
refresh_error: refreshErrorMessage,
},
});

View File

@@ -239,13 +239,23 @@ export function DashboardControls({
}
async function onDomainAction(
domainId: string,
domain: Domain,
action: "suspend" | "unsuspend",
) {
if (!domain.subscription_id) {
setMessage(
"Domain is not linked to a subscription yet. Run Sync Now (or backfill links) and try again.",
);
return;
}
setLoading(true);
setMessage(null);
try {
await runRequest(`/api/plesk/domains/${domainId}/action`, { action });
await runRequest(`/api/plesk/domains/${domain.id}/action`, {
action,
subscription_id: domain.subscription_id,
});
setMessage("Domain action completed.");
window.location.reload();
} catch (error) {
@@ -633,7 +643,7 @@ export function DashboardControls({
<td className="px-2 py-2">
<div className="flex gap-2">
<button
onClick={() => onDomainAction(domain.id, "suspend")}
onClick={() => onDomainAction(domain, "suspend")}
disabled={
loading || isDomainSuspended(domain.status)
}
@@ -642,9 +652,7 @@ export function DashboardControls({
Suspend
</button>
<button
onClick={() =>
onDomainAction(domain.id, "unsuspend")
}
onClick={() => onDomainAction(domain, "unsuspend")}
disabled={loading || isDomainActive(domain.status)}
className="rounded border border-emerald-300 px-2 py-1 text-xs text-emerald-700 disabled:cursor-not-allowed disabled:opacity-60"
>