Files
portabase/app/api/agent/[agentId]/status/helpers.ts
T

249 lines
9.0 KiB
TypeScript
Raw Normal View History

2024-11-29 12:54:34 +01:00
import {NextResponse} from "next/server";
import {Body} from "./route";
import {isUuidv4} from "@/utils/verify-uuid";
import {Agent} from "@/db/schema/08_agent";
2026-02-08 20:42:42 +01:00
import {Database, DatabaseWith} from "@/db/schema/07_database";
2025-07-16 18:15:28 +02:00
import * as drizzleDb from "@/db";
2026-02-08 20:42:42 +01:00
import {db, db as dbClient} from "@/db";
import {and, eq, inArray} from "drizzle-orm";
2026-01-18 20:20:10 +01:00
import {dbmsEnumSchema, EDbmsSchema} from "@/db/schema/types";
import {withUpdatedAt} from "@/db/utils";
2026-01-17 17:27:33 +01:00
import type {StorageInput} from "@/features/storages/types";
import {dispatchStorage} from "@/features/storages/dispatch";
2026-02-12 20:18:26 +01:00
import {Setting} from "@/db/schema/01_setting";
2024-11-29 12:54:34 +01:00
2026-02-12 20:18:26 +01:00
export async function handleDatabases(body: Body, agent: Agent, lastContact: Date, settings: Setting) {
2024-11-29 12:54:34 +01:00
const databasesResponse = [];
2026-02-08 20:42:42 +01:00
const formatDatabase = (database: DatabaseWith, backupAction: boolean, restoreAction: boolean, UrlBackup: string | null, storages: PingDatabaseStorageChannels[], urlMeta: string | null) => ({
2025-07-16 18:15:28 +02:00
generatedId: database.agentDatabaseId,
2024-11-29 12:54:34 +01:00
dbms: database.dbms,
2026-02-08 20:42:42 +01:00
storages: storages,
2026-02-12 20:18:26 +01:00
encrypt: settings.encryption,
2024-11-29 12:54:34 +01:00
data: {
backup: {
action: backupAction,
2024-12-13 14:05:40 +01:00
cron: database.backupPolicy,
2024-11-29 12:54:34 +01:00
},
restore: {
2024-12-11 20:46:26 +01:00
action: restoreAction,
file: UrlBackup,
2026-02-08 20:42:42 +01:00
metaFile: urlMeta
2024-11-29 12:54:34 +01:00
},
},
});
for (const db of body.databases) {
2025-07-16 18:15:28 +02:00
const existingDatabase = await dbClient.query.database.findFirst({
2026-02-08 20:42:42 +01:00
where: eq(drizzleDb.schemas.database.agentDatabaseId, db.generatedId),
with: {
project: true
}
2024-11-29 12:54:34 +01:00
});
let backupAction: boolean = false
2024-12-11 20:46:26 +01:00
let restoreAction: boolean = false
2026-02-08 20:42:42 +01:00
let urlBackup: string | null = null;
let urlMeta: string | null = null
2024-11-29 12:54:34 +01:00
if (!existingDatabase) {
if (!isUuidv4(db.generatedId)) {
return NextResponse.json(
{error: "generatedId is not a valid uuid"},
{status: 500}
2024-11-29 12:54:34 +01:00
);
}
2026-01-18 20:20:10 +01:00
if (!dbmsEnumSchema.safeParse(db.dbms).success) {
console.log(`Database type not available: ${db.dbms}`);
continue;
}
2025-07-16 18:15:28 +02:00
const [databaseCreated] = await dbClient
.insert(drizzleDb.schemas.database)
.values({
2024-11-29 12:54:34 +01:00
agentId: agent.id,
name: db.name,
2025-07-16 20:16:18 +02:00
dbms: db.dbms as EDbmsSchema,
2025-07-16 18:15:28 +02:00
agentDatabaseId: db.generatedId,
2024-11-29 12:54:34 +01:00
lastContact: lastContact,
2025-07-16 18:15:28 +02:00
})
.returning();
2024-11-29 12:54:34 +01:00
2026-02-08 20:42:42 +01:00
2024-11-29 12:54:34 +01:00
if (databaseCreated) {
2026-02-08 20:42:42 +01:00
const storages = await getDatabaseStorageChannels(databaseCreated.id)
databasesResponse.push(formatDatabase(databaseCreated, backupAction, restoreAction, urlBackup, storages, null));
2024-11-29 12:54:34 +01:00
}
} else {
2025-07-16 18:15:28 +02:00
const [databaseUpdated] = await dbClient
.update(drizzleDb.schemas.database)
2025-12-21 11:57:07 +01:00
.set(withUpdatedAt({
name: db.name,
agentId: agent.id,
lastContact: lastContact
2025-12-21 11:57:07 +01:00
}))
2025-07-16 18:15:28 +02:00
.where(eq(drizzleDb.schemas.database.id, existingDatabase.id))
.returning();
const activeBackup = await dbClient.query.backup.findFirst({
where: and(
eq(drizzleDb.schemas.backup.databaseId, databaseUpdated.id),
inArray(drizzleDb.schemas.backup.status, ["waiting", "ongoing"])
)
})
2025-07-16 18:15:28 +02:00
const restoration = await dbClient.query.restoration.findFirst({
2026-01-17 17:27:33 +01:00
where: and(eq(drizzleDb.schemas.restoration.databaseId, databaseUpdated.id), eq(drizzleDb.schemas.restoration.status, "waiting")),
with: {
backupStorage: true
}
2024-12-11 20:46:26 +01:00
})
if (activeBackup && activeBackup.status == "waiting") {
backupAction = true
2025-07-16 18:15:28 +02:00
await dbClient
.update(drizzleDb.schemas.backup)
.set(withUpdatedAt({status: "ongoing"}))
.where(eq(drizzleDb.schemas.backup.id, activeBackup.id));
}
if (restoration) {
2024-12-11 20:46:26 +01:00
restoreAction = true
2026-01-17 17:27:33 +01:00
if (!restoration.backupStorage || restoration.backupStorage.status != "success" || !restoration.backupStorage.path) {
restoreAction = false
2026-01-17 19:23:15 +01:00
continue;
}
2026-01-17 17:27:33 +01:00
const input: StorageInput = {
action: "get",
data: {
path: restoration.backupStorage.path,
signedUrl: true,
},
metadata: {
storageId: restoration.backupStorage.storageChannelId,
fileKind: "backups"
}
2026-01-17 17:27:33 +01:00
};
2025-07-16 18:15:28 +02:00
2026-02-08 20:42:42 +01:00
const inputMeta: StorageInput = {
action: "get",
data: {
path: `${restoration.backupStorage.path}.meta`,
signedUrl: true,
},
metadata: {
storageId: restoration.backupStorage.storageChannelId,
fileKind: "backups"
}
};
try {
2026-01-17 17:27:33 +01:00
const result = await dispatchStorage(input, undefined, restoration.backupStorage.storageChannelId);
2026-02-08 20:42:42 +01:00
const resultMeta = await dispatchStorage(inputMeta, undefined, restoration.backupStorage.storageChannelId);
2026-01-17 17:27:33 +01:00
if (result.success) {
2026-02-08 20:42:42 +01:00
urlBackup = result.url ?? null;
urlMeta = resultMeta.url ?? null
} else {
await dbClient
.update(drizzleDb.schemas.restoration)
.set({status: "failed"})
.where(eq(drizzleDb.schemas.restoration.id, restoration.id));
2026-01-17 17:27:33 +01:00
const errorMessage = "Failed to get backup URL";
console.error("Restoration failed: ", errorMessage);
continue;
}
} catch (err) {
console.error("Restoration crashed unexpectedly:", err);
await dbClient
.update(drizzleDb.schemas.restoration)
.set({status: "failed"})
.where(eq(drizzleDb.schemas.restoration.id, restoration.id));
continue;
}
2026-01-17 17:27:33 +01:00
2025-07-16 18:15:28 +02:00
await dbClient
.update(drizzleDb.schemas.restoration)
.set({status: "ongoing"})
2025-07-16 18:15:28 +02:00
.where(eq(drizzleDb.schemas.restoration.id, restoration.id));
2024-12-11 20:46:26 +01:00
}
2026-02-08 20:42:42 +01:00
const storages = await getDatabaseStorageChannels(databaseUpdated.id)
databasesResponse.push(formatDatabase(databaseUpdated, backupAction, restoreAction, urlBackup, storages, urlMeta));
2024-11-29 12:54:34 +01:00
}
}
2026-02-08 20:42:42 +01:00
2024-11-29 12:54:34 +01:00
return databasesResponse;
}
2026-02-08 20:42:42 +01:00
type PingDatabaseStorageChannels = {
id: string;
config: any
provider: string
}
async function getDatabaseStorageChannels(databaseId: string): Promise<PingDatabaseStorageChannels[]> {
const database = await db.query.database.findFirst({
where: eq(drizzleDb.schemas.database.id, databaseId),
with: {
project: true,
retentionPolicy: true,
alertPolicies: true,
storagePolicies: true
}
});
if (!database) {
return []
}
const settings = await db.query.setting.findFirst({
where: eq(drizzleDb.schemas.setting.name, "system"),
with: {storageChannel: true},
});
const defaultStorageChannel: PingDatabaseStorageChannels[] = settings?.storageChannel
? [{
id: settings.storageChannel.id,
provider: settings.storageChannel.provider,
config: settings.storageChannel.config,
}]
: [];
const enabledDatabaseStorageChannels = await Promise.all(
(database.storagePolicies ?? [])
.filter(p => p.enabled)
.map(async policy => {
const storageChannel = await db.query.storageChannel.findFirst({
where: eq(drizzleDb.schemas.storageChannel.id, policy.storageChannelId),
});
if (!storageChannel) return null;
return {
id: storageChannel.id,
config: storageChannel.config,
provider: storageChannel.provider,
} as PingDatabaseStorageChannels;
})
);
const filteredChannels: PingDatabaseStorageChannels[] = enabledDatabaseStorageChannels.filter(
(c): c is PingDatabaseStorageChannels => c !== null
);
return filteredChannels.length > 0 ? filteredChannels : defaultStorageChannel;
}