feat(integrations): добавить полный sync bindings в VPS Tracker
Endpoint для запроса полной синхронизации по integration token. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -3,6 +3,7 @@ import type { FastifyInstance } from "fastify";
|
||||
import { vpsTrackerEventSchema } from "@cfdm/shared";
|
||||
import { getAppSettingsSecrets } from "@cfdm/db";
|
||||
import * as vpsTrackerEvents from "../services/vps-tracker-events.js";
|
||||
import { syncAllToVpsTracker } from "../services/vps-tracker-sync.js";
|
||||
|
||||
function verifyBearer(authHeader: string | undefined, expected: string): boolean {
|
||||
if (!authHeader?.startsWith("Bearer ")) return false;
|
||||
@@ -41,4 +42,29 @@ export async function integrationsVpsTrackerRoutes(app: FastifyInstance) {
|
||||
);
|
||||
return { ok: true, reconciled };
|
||||
});
|
||||
|
||||
/** Запрос полной синхронизации bindings → VPS Tracker (вызывает VPS Tracker). */
|
||||
app.post("/integrations/vps-tracker/sync", async (req, reply) => {
|
||||
const secrets = getAppSettingsSecrets(app.db);
|
||||
const token = secrets.vpsTrackerIntegrationToken;
|
||||
if (!token) {
|
||||
return reply.status(503).send({
|
||||
ok: false,
|
||||
error: "Integration not configured",
|
||||
});
|
||||
}
|
||||
if (!verifyBearer(req.headers.authorization, token)) {
|
||||
return reply.status(401).send({ ok: false, error: "Unauthorized" });
|
||||
}
|
||||
|
||||
const result = await syncAllToVpsTracker(app.db);
|
||||
if (!result.ok) {
|
||||
return reply.status(502).send({
|
||||
ok: false,
|
||||
error: result.error ?? "Sync failed",
|
||||
count: result.count,
|
||||
});
|
||||
}
|
||||
return { ok: true, count: result.count };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,6 +52,29 @@ export function buildServiceSyncBindings(
|
||||
return items;
|
||||
}
|
||||
|
||||
export function buildAllSyncBindings(db: Db): CfdmBindingSyncItem[] {
|
||||
const bindings = repos.listAllBindings(db);
|
||||
return bindings.map((binding) => {
|
||||
const serviceIps = repos.listServiceIps(db, binding.service_id);
|
||||
const ips =
|
||||
binding.target_ips.length > 0
|
||||
? binding.target_ips
|
||||
: serviceIps.length > 0
|
||||
? serviceIps
|
||||
: [];
|
||||
return {
|
||||
bindingId: binding.id,
|
||||
serviceId: binding.service_id,
|
||||
serviceName: binding.service_name,
|
||||
serviceSlug: binding.service_slug,
|
||||
fqdn: fqdnToDisplay(binding.hostname, binding.zone_name),
|
||||
zoneName: binding.zone_name,
|
||||
hostname: binding.hostname,
|
||||
ips,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function syncServiceToVpsTracker(
|
||||
db: Db,
|
||||
serviceId: number,
|
||||
@@ -91,6 +114,54 @@ export async function syncServiceToVpsTracker(
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncAllToVpsTracker(db: Db): Promise<{
|
||||
ok: boolean;
|
||||
count: number;
|
||||
error?: string;
|
||||
}> {
|
||||
const config = getAppSettingsSecrets(db);
|
||||
if (!config.vpsTrackerSyncEnabled) {
|
||||
return { ok: false, count: 0, error: "Синхронизация выключена в CFDM" };
|
||||
}
|
||||
|
||||
const baseUrl = config.vpsTrackerUrl.replace(/\/$/, "");
|
||||
const token = config.vpsTrackerIntegrationToken;
|
||||
if (!baseUrl) {
|
||||
return { ok: false, count: 0, error: "Укажите URL VPS Tracker" };
|
||||
}
|
||||
if (!token) {
|
||||
return { ok: false, count: 0, error: "Укажите integration token" };
|
||||
}
|
||||
|
||||
const bindings = buildAllSyncBindings(db);
|
||||
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/integrations/cfdm/sync-bindings`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ bindings, fullSync: true }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
return {
|
||||
ok: false,
|
||||
count: 0,
|
||||
error: `HTTP ${res.status}: ${await res.text()}`,
|
||||
};
|
||||
}
|
||||
touchVpsTrackerSync(db);
|
||||
return { ok: true, count: bindings.length };
|
||||
} catch (err) {
|
||||
return {
|
||||
ok: false,
|
||||
count: 0,
|
||||
error: err instanceof Error ? err.message : "Ошибка сети",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function pingVpsTracker(db: Db): Promise<{
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
|
||||
Vendored
+1
@@ -1480,6 +1480,7 @@ declare const cfdmSyncBindingsBodySchema: z.ZodObject<{
|
||||
ips: z.ZodArray<z.ZodString>;
|
||||
deleted: z.ZodOptional<z.ZodBoolean>;
|
||||
}, z.core.$strip>>;
|
||||
fullSync: z.ZodOptional<z.ZodBoolean>;
|
||||
}, z.core.$strip>;
|
||||
type CfdmBindingSyncItem = z.infer<typeof cfdmBindingSyncItemSchema>;
|
||||
declare const appSettingsPatchSchema: z.ZodObject<{
|
||||
|
||||
Vendored
+6
-1
@@ -582,7 +582,12 @@ var cfdmBindingSyncItemSchema = z3.object({
|
||||
deleted: z3.boolean().optional()
|
||||
});
|
||||
var cfdmSyncBindingsBodySchema = z3.object({
|
||||
bindings: z3.array(cfdmBindingSyncItemSchema).min(1)
|
||||
bindings: z3.array(cfdmBindingSyncItemSchema),
|
||||
/** Полная пересинхронизация: удалить CFDM-домены, которых нет в payload. */
|
||||
fullSync: z3.boolean().optional()
|
||||
}).refine((data) => data.fullSync === true || data.bindings.length >= 1, {
|
||||
message: "bindings \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D, \u0435\u0441\u043B\u0438 fullSync \u043D\u0435 \u0437\u0430\u0434\u0430\u043D",
|
||||
path: ["bindings"]
|
||||
});
|
||||
var appSettingsPatchSchema = z3.object({
|
||||
vpsTrackerUrl: z3.string().url().or(z3.literal("")).optional(),
|
||||
|
||||
@@ -13,7 +13,12 @@ export const cfdmBindingSyncItemSchema = z.object({
|
||||
});
|
||||
|
||||
export const cfdmSyncBindingsBodySchema = z.object({
|
||||
bindings: z.array(cfdmBindingSyncItemSchema).min(1),
|
||||
bindings: z.array(cfdmBindingSyncItemSchema),
|
||||
/** Полная пересинхронизация: удалить CFDM-домены, которых нет в payload. */
|
||||
fullSync: z.boolean().optional(),
|
||||
}).refine((data) => data.fullSync === true || data.bindings.length >= 1, {
|
||||
message: "bindings обязателен, если fullSync не задан",
|
||||
path: ["bindings"],
|
||||
});
|
||||
|
||||
export type CfdmBindingSyncItem = z.infer<typeof cfdmBindingSyncItemSchema>;
|
||||
|
||||
Reference in New Issue
Block a user