refactor: simplify snapshot file explorer

This commit is contained in:
Nicolas Meienberger
2025-11-04 14:57:22 +01:00
parent 11ca80a929
commit d1e46918ec
16 changed files with 309 additions and 258 deletions

View File

@@ -5,6 +5,7 @@ import {
createRepositoryDto,
deleteRepositoryDto,
getRepositoryDto,
getSnapshotDetailsDto,
listRepositoriesDto,
listSnapshotFilesDto,
listSnapshotFilesQuery,
@@ -14,6 +15,7 @@ import {
restoreSnapshotDto,
type DeleteRepositoryDto,
type GetRepositoryDto,
type GetSnapshotDetailsDto,
type ListRepositoriesDto,
type ListSnapshotFilesDto,
type ListSnapshotsDto,
@@ -71,6 +73,27 @@ export const repositoriesController = new Hono()
return c.json<ListSnapshotsDto>(snapshots, 200);
})
.get("/:name/snapshots/:snapshotId", getSnapshotDetailsDto, async (c) => {
const { name, snapshotId } = c.req.param();
const snapshot = await repositoriesService.getSnapshotDetails(name, snapshotId);
let duration = 0;
if (snapshot.summary) {
const { backup_start, backup_end } = snapshot.summary;
duration = new Date(backup_end).getTime() - new Date(backup_start).getTime();
}
const response = {
short_id: snapshot.short_id,
duration,
time: new Date(snapshot.time).getTime(),
paths: snapshot.paths,
size: snapshot.summary?.total_bytes_processed || 0,
summary: snapshot.summary,
};
return c.json<GetSnapshotDetailsDto>(response, 200);
})
.get(
"/:name/snapshots/:snapshotId/files",
listSnapshotFilesDto,
@@ -81,7 +104,7 @@ export const repositoriesController = new Hono()
const result = await repositoriesService.listSnapshotFiles(name, snapshotId, path);
// c.header("Cache-Control", "max-age=300, stale-while-revalidate=600");
c.header("Cache-Control", "max-age=300, stale-while-revalidate=600");
return c.json<ListSnapshotFilesDto>(result, 200);
},

View File

@@ -163,6 +163,29 @@ export const listSnapshotsDto = describeRoute({
},
});
/**
* Get snapshot details
*/
export const getSnapshotDetailsResponse = snapshotSchema;
export type GetSnapshotDetailsDto = typeof getSnapshotDetailsResponse.infer;
export const getSnapshotDetailsDto = describeRoute({
description: "Get details of a specific snapshot",
tags: ["Repositories"],
operationId: "getSnapshotDetails",
responses: {
200: {
description: "Snapshot details",
content: {
"application/json": {
schema: resolver(getSnapshotDetailsResponse),
},
},
},
},
});
/**
* List files in a snapshot
*/

View File

@@ -184,6 +184,25 @@ const restoreSnapshot = async (
};
};
const getSnapshotDetails = async (name: string, snapshotId: string) => {
const repository = await db.query.repositoriesTable.findFirst({
where: eq(repositoriesTable.name, name),
});
if (!repository) {
throw new NotFoundError("Repository not found");
}
const snapshots = await restic.snapshots(repository.config);
const snapshot = snapshots.find((snap) => snap.id === snapshotId || snap.short_id === snapshotId);
if (!snapshot) {
throw new NotFoundError("Snapshot not found");
}
return snapshot;
};
export const repositoriesService = {
listRepositories,
createRepository,
@@ -192,4 +211,5 @@ export const repositoriesService = {
listSnapshots,
listSnapshotFiles,
restoreSnapshot,
getSnapshotDetails,
};

View File

@@ -8,7 +8,6 @@ import { REPOSITORY_BASE, RESTIC_PASS_FILE } from "../core/constants";
import { logger } from "./logger";
import { cryptoUtils } from "./crypto";
import type { RetentionPolicy } from "../modules/backups/backups.dto";
import { getVolumePath } from "../modules/volumes/helpers";
const backupOutputSchema = type({
message_type: "'summary'",