Files
pleskSaas/lib/plesk/sync.ts

250 lines
7.3 KiB
TypeScript

import type { Database } from "@/lib/types";
import { listPleskDomains, listPleskSubscriptions } from "@/lib/plesk/client";
type SupabaseLike = any;
type SyncResult = {
instanceId: string;
agencyId: string;
skipped: boolean;
reason?: string;
subscriptionsUpserted: number;
domainsUpserted: number;
};
type InstanceRow = Pick<
Database["public"]["Tables"]["plesk_instances"]["Row"],
| "id"
| "agency_id"
| "base_url"
| "auth_type"
| "encrypted_secret"
| "last_connected_at"
>;
const MIN_INTERVAL_MS = 3 * 60 * 1000;
export async function syncPleskInstance(
supabase: SupabaseLike,
instance: InstanceRow,
actorUserId?: string | null,
action = "sync",
): Promise<SyncResult> {
const startedAtIso = new Date().toISOString();
await supabase
.from("plesk_instances")
.update({
status: "syncing",
last_sync_error: null,
})
.eq("id", instance.id)
.eq("agency_id", instance.agency_id);
const last = instance.last_connected_at
? new Date(instance.last_connected_at).getTime()
: 0;
if (last && Date.now() - last < MIN_INTERVAL_MS) {
await supabase
.from("plesk_instances")
.update({
status: "connected",
last_sync_at: startedAtIso,
last_sync_status: "skipped",
last_sync_error: "recently_synced",
})
.eq("id", instance.id)
.eq("agency_id", instance.agency_id);
return {
instanceId: instance.id,
agencyId: instance.agency_id,
skipped: true,
reason: "recently_synced",
subscriptionsUpserted: 0,
domainsUpserted: 0,
};
}
try {
const [subscriptionsRaw, domainsRaw] = await Promise.all([
listPleskSubscriptions({
baseUrl: instance.base_url,
authType: instance.auth_type,
encryptedSecret: instance.encrypted_secret,
}),
listPleskDomains({
baseUrl: instance.base_url,
authType: instance.auth_type,
encryptedSecret: instance.encrypted_secret,
}),
]);
const subscriptionsForUpsert = subscriptionsRaw
.map((item: any) => {
const sourceId =
item?.plesk_subscription_id ??
item?.name ??
item?.id ??
item?.guid ??
item?.subscription_id;
if (!sourceId) return null;
return {
agency_id: instance.agency_id,
plesk_instance_id: instance.id,
plesk_subscription_id: String(sourceId),
name: item?.name ? String(item.name) : String(sourceId),
status: item?.status ? String(item.status) : null,
owner_login: item?.owner_login
? String(item.owner_login)
: item?.owner?.login
? String(item.owner.login)
: null,
plan_name: item?.plan_name
? String(item.plan_name)
: item?.service_plan?.name
? String(item.service_plan.name)
: null,
};
})
.filter(Boolean) as Array<Record<string, unknown>>;
if (subscriptionsForUpsert.length > 0) {
const { error } = await supabase
.from("plesk_subscriptions")
.upsert(subscriptionsForUpsert, {
onConflict: "agency_id,plesk_subscription_id",
});
if (error) throw new Error(error.message);
}
const { data: localSubscriptions, error: localSubscriptionsError } =
await supabase
.from("plesk_subscriptions")
.select("id, plesk_subscription_id, status")
.eq("agency_id", instance.agency_id)
.eq("plesk_instance_id", instance.id);
if (localSubscriptionsError) {
throw new Error(localSubscriptionsError.message);
}
const subscriptionByExternalId = new Map<
string,
{ id: string; status: string | null }
>(
(localSubscriptions ?? [])
.filter((row: any) => row?.plesk_subscription_id && row?.id)
.map((row: any) => [
String(row.plesk_subscription_id),
{
id: String(row.id),
status: row?.status ? String(row.status) : null,
},
]),
);
const domainsForUpsert = domainsRaw
.map((item: any) => {
const domainId = item?.id ?? item?.guid ?? item?.name;
const domainName = item?.name ?? item?.domain_name ?? item?.domain;
const subExternalId =
item?.subscription?.id ?? item?.subscription_id ?? item?.webspace_id;
const externalSubscriptionId = subExternalId
? String(subExternalId)
: null;
const localSubscription = externalSubscriptionId
? subscriptionByExternalId.get(externalSubscriptionId)
: undefined;
const rawCreated = item?.created_at ?? item?.created;
const sourceCreatedAt = rawCreated
? new Date(String(rawCreated)).toISOString()
: null;
const aliasesCount =
typeof item?.aliases_count === "number"
? item.aliases_count
: Array.isArray(item?.aliases)
? item.aliases.length
: null;
if (!domainId || !domainName) return null;
return {
agency_id: instance.agency_id,
plesk_instance_id: instance.id,
subscription_id: localSubscription?.id ?? null,
external_subscription_id: externalSubscriptionId,
plesk_domain_id: String(domainId),
domain_name: String(domainName),
status: localSubscription?.status ?? "unknown",
hosting_type: item?.hosting_type ? String(item.hosting_type) : null,
source_created_at: sourceCreatedAt,
aliases_count: aliasesCount,
};
})
.filter(Boolean) as Array<Record<string, unknown>>;
if (domainsForUpsert.length > 0) {
const { error } = await supabase
.from("plesk_domains")
.upsert(domainsForUpsert, {
onConflict: "agency_id,plesk_domain_id",
});
if (error) throw new Error(error.message);
}
await supabase
.from("plesk_instances")
.update({
last_connected_at: new Date().toISOString(),
status: "connected",
error_message: null,
last_sync_at: startedAtIso,
last_sync_status: "success",
last_sync_error: null,
last_sync_subscriptions: subscriptionsForUpsert.length,
last_sync_domains: domainsForUpsert.length,
})
.eq("id", instance.id)
.eq("agency_id", instance.agency_id);
await supabase.from("actions_log").insert({
agency_id: instance.agency_id,
actor_user_id: actorUserId ?? null,
target_type: "plesk_instance",
target_id: instance.id,
action,
status: "success",
metadata: {
subscriptions_seen: subscriptionsRaw.length,
domains_seen: domainsRaw.length,
},
});
return {
instanceId: instance.id,
agencyId: instance.agency_id,
skipped: false,
subscriptionsUpserted: subscriptionsForUpsert.length,
domainsUpserted: domainsForUpsert.length,
};
} catch (error) {
const message =
error instanceof Error ? error.message.slice(0, 1000) : "Sync failed";
await supabase
.from("plesk_instances")
.update({
status: "error",
error_message: message,
last_sync_at: startedAtIso,
last_sync_status: "failed",
last_sync_error: message,
})
.eq("id", instance.id)
.eq("agency_id", instance.agency_id);
throw error;
}
}