feat(snapshots): list files in snapshots api

This commit is contained in:
Nicolas Meienberger
2025-10-30 18:58:57 +01:00
parent ed73ca73fb
commit b80a187108
8 changed files with 251 additions and 2 deletions

View File

@@ -8,10 +8,13 @@ import {
listRepositoriesDto,
listSnapshotsDto,
listSnapshotsFilters,
listSnapshotFilesDto,
listSnapshotFilesQuery,
type DeleteRepositoryDto,
type GetRepositoryDto,
type ListRepositoriesDto,
type ListSnapshotsDto,
type ListSnapshotFilesDto,
} from "./repositories.dto";
import { repositoriesService } from "./repositories.service";
@@ -68,4 +71,14 @@ export const repositoriesController = new Hono()
c.header("Cache-Control", "max-age=30, stale-while-revalidate=300");
return c.json<ListSnapshotsDto>(response, 200);
})
.get("/:name/snapshots/:snapshotId/files", listSnapshotFilesDto, validator("query", listSnapshotFilesQuery), async (c) => {
const { name, snapshotId } = c.req.param();
const { path } = c.req.valid("query");
const result = await repositoriesService.listSnapshotFiles(name, snapshotId, path);
c.header("Cache-Control", "max-age=300, stale-while-revalidate=600");
return c.json<ListSnapshotFilesDto>(result, 200);
});

View File

@@ -164,3 +164,52 @@ export const listSnapshotsDto = describeRoute({
},
},
});
/**
* List files in a snapshot
*/
export const snapshotFileNodeSchema = type({
name: "string",
type: "string",
path: "string",
uid: "number?",
gid: "number?",
size: "number?",
mode: "number?",
mtime: "string?",
atime: "string?",
ctime: "string?",
});
export const listSnapshotFilesResponse = type({
snapshot: type({
id: "string",
short_id: "string",
time: "string",
hostname: "string",
paths: "string[]",
}),
files: snapshotFileNodeSchema.array(),
});
export type ListSnapshotFilesDto = typeof listSnapshotFilesResponse.infer;
export const listSnapshotFilesQuery = type({
path: "string?",
});
export const listSnapshotFilesDto = describeRoute({
description: "List files and directories in a snapshot",
tags: ["Repositories"],
operationId: "listSnapshotFiles",
responses: {
200: {
description: "List of files and directories in the snapshot",
content: {
"application/json": {
schema: resolver(listSnapshotFilesResponse),
},
},
},
},
});

View File

@@ -134,10 +134,38 @@ const listSnapshots = async (name: string, volumeId?: number) => {
return snapshots;
};
const listSnapshotFiles = async (name: string, snapshotId: string, path?: string) => {
const repository = await db.query.repositoriesTable.findFirst({
where: eq(repositoriesTable.name, name),
});
if (!repository) {
throw new NotFoundError("Repository not found");
}
const result = await restic.ls(repository.config, snapshotId, path);
if (!result.snapshot) {
throw new NotFoundError("Snapshot not found or empty");
}
return {
snapshot: {
id: result.snapshot.id,
short_id: result.snapshot.short_id,
time: result.snapshot.time,
hostname: result.snapshot.hostname,
paths: result.snapshot.paths,
},
files: result.nodes,
};
};
export const repositoriesService = {
listRepositories,
createRepository,
getRepository,
deleteRepository,
listSnapshots,
listSnapshotFiles,
};