Merge branch 'feature' into feat/refresh

# Conflicts:
#	app/api/agent/[agentId]/backup/route.ts
#	app/api/events/route.ts
#	src/components/wrappers/dashboard/database/backup/actions/backup-actions-form.tsx
#	src/components/wrappers/dashboard/database/backup/actions/backup-actions.action.ts
#	src/components/wrappers/dashboard/database/channels-policy/policy-form.tsx
#	src/lib/tasks/database/index.ts
This commit is contained in:
charlesgauthereau
2026-02-11 12:10:33 +01:00
49 changed files with 3039 additions and 2806 deletions
+96 -52
View File
@@ -2,9 +2,9 @@ import {NextResponse} from "next/server";
import {Body} from "./route";
import {isUuidv4} from "@/utils/verify-uuid";
import {Agent} from "@/db/schema/08_agent";
import {Database} from "@/db/schema/07_database";
import {Database, DatabaseWith} from "@/db/schema/07_database";
import * as drizzleDb from "@/db";
import {db as dbClient} from "@/db";
import {db, db as dbClient} from "@/db";
import {and, eq, inArray} from "drizzle-orm";
import {dbmsEnumSchema, EDbmsSchema} from "@/db/schema/types";
import {withUpdatedAt} from "@/db/utils";
@@ -14,9 +14,11 @@ import {dispatchStorage} from "@/features/storages/dispatch";
export async function handleDatabases(body: Body, agent: Agent, lastContact: Date) {
const databasesResponse = [];
const formatDatabase = (database: Database, backupAction: boolean, restoreAction: boolean, UrlBackup: string) => ({
const formatDatabase = (database: DatabaseWith, backupAction: boolean, restoreAction: boolean, UrlBackup: string | null, storages: PingDatabaseStorageChannels[], urlMeta: string | null) => ({
generatedId: database.agentDatabaseId,
dbms: database.dbms,
storages: storages,
encrypt: false,
data: {
backup: {
action: backupAction,
@@ -25,6 +27,7 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
restore: {
action: restoreAction,
file: UrlBackup,
metaFile: urlMeta
},
},
});
@@ -32,12 +35,16 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
for (const db of body.databases) {
const existingDatabase = await dbClient.query.database.findFirst({
where: eq(drizzleDb.schemas.database.agentDatabaseId, db.generatedId)
where: eq(drizzleDb.schemas.database.agentDatabaseId, db.generatedId),
with: {
project: true
}
});
let backupAction: boolean = false
let restoreAction: boolean = false
let urlBackup: string = ""
let urlBackup: string | null = null;
let urlMeta: string | null = null
if (!existingDatabase) {
if (!isUuidv4(db.generatedId)) {
@@ -63,8 +70,11 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
})
.returning();
if (databaseCreated) {
databasesResponse.push(formatDatabase(databaseCreated, backupAction, restoreAction, urlBackup));
const storages = await getDatabaseStorageChannels(databaseCreated.id)
databasesResponse.push(formatDatabase(databaseCreated, backupAction, restoreAction, urlBackup, storages, null));
}
} else {
@@ -78,7 +88,6 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
.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),
@@ -86,7 +95,6 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
)
})
const restoration = await dbClient.query.restoration.findFirst({
where: and(eq(drizzleDb.schemas.restoration.databaseId, databaseUpdated.id), eq(drizzleDb.schemas.restoration.status, "waiting")),
with: {
@@ -94,7 +102,6 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
}
})
if (activeBackup && activeBackup.status == "waiting") {
backupAction = true
@@ -107,13 +114,11 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
if (restoration) {
restoreAction = true
if (!restoration.backupStorage || restoration.backupStorage.status != "success" || !restoration.backupStorage.path) {
restoreAction = false
continue;
}
const input: StorageInput = {
action: "get",
data: {
@@ -126,12 +131,26 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
}
};
const inputMeta: StorageInput = {
action: "get",
data: {
path: `${restoration.backupStorage.path}.meta`,
signedUrl: true,
},
metadata: {
storageId: restoration.backupStorage.storageChannelId,
fileKind: "backups"
}
};
try {
const result = await dispatchStorage(input, undefined, restoration.backupStorage.storageChannelId);
const resultMeta = await dispatchStorage(inputMeta, undefined, restoration.backupStorage.storageChannelId);
if (result.success) {
urlBackup = result.url ?? "";
urlBackup = result.url ?? null;
urlMeta = resultMeta.url ?? null
} else {
await dbClient
.update(drizzleDb.schemas.restoration)
@@ -151,53 +170,78 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
continue;
}
// const fileName = backupToRestore?.file
//
// let data: SafeActionResult<string, ZodString, readonly [], {
// _errors?: string[] | undefined;
// }, readonly [], ServerActionResult<string>, object> | undefined
//
// try {
//
// if (settings.storage == "local") {
// data = await getFileUrlPresignedLocal({fileName: fileName!})
// } else if (settings.storage == "s3") {
//
// data = await getFileUrlPreSignedS3Action(`backups/${backupToRestore?.database.project?.slug}/${fileName}`);
// }
//
// if (data?.data?.success) {
// urlBackup = data.data.value ?? "";
// } else {
// await dbClient
// .update(drizzleDb.schemas.restoration)
// .set({status: "failed"})
// .where(eq(drizzleDb.schemas.restoration.id, restoration.id));
//
// // @ts-ignore
// const errorMessage = data?.data?.actionError?.message || "Failed to get presigned 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;
// }
await dbClient
.update(drizzleDb.schemas.restoration)
.set({status: "ongoing"})
.where(eq(drizzleDb.schemas.restoration.id, restoration.id));
}
databasesResponse.push(formatDatabase(databaseUpdated, backupAction, restoreAction, urlBackup));
const storages = await getDatabaseStorageChannels(databaseUpdated.id)
databasesResponse.push(formatDatabase(databaseUpdated, backupAction, restoreAction, urlBackup, storages, urlMeta));
}
}
return databasesResponse;
}
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;
}
+4
View File
@@ -6,6 +6,7 @@ import {EDbmsSchema} from "@/db/schema/types";
import {eq} from "drizzle-orm";
import {isUuidv4} from "@/utils/verify-uuid";
import {withUpdatedAt} from "@/db/utils";
import {eventEmitter} from "@/features/shared/event";
export type databaseAgent = {
name: string,
@@ -25,12 +26,14 @@ export async function POST(
) {
try {
const agentId = (await params).agentId
console.log(agentId)
const body: Body = await request.json();
const lastContact = new Date();
let message: string
if (!isUuidv4(agentId)) {
message = "agentId is not a valid uuid"
console.error(message)
return NextResponse.json(
{error: "agentId is not a valid uuid"},
{status: 500}
@@ -64,6 +67,7 @@ export async function POST(
databases: databasesResponse
}
return Response.json(response)
} catch (error) {
console.error('Error in POST handler:', error);