Step 8: add set_favorite, get_file_versions, restore_file_version tools

- set_favorite: PROPPATCH oc:favorite + PROPFIND for updated metadata
- get_file_versions: PROPFIND on /versions/{user}/versions/{fileId}
- restore_file_version: MOVE to /versions/{user}/restore
- Uses existing webdav helpers (buildProppatchBody, buildVersionsPropfindBody, parseVersionsResponse)
This commit is contained in:
2026-05-11 17:09:58 +02:00
parent 66112abe9c
commit f87c82d36e
+199
View File
@@ -1319,6 +1319,145 @@ async function handleTrashList(
return makeToolResponse(items);
}
async function handleTrashRestore(
args: { trashPath: string },
client: NextcloudClient
): Promise<ToolResponse> {
if (!args.trashPath) return makeErrorResponse("trashPath is required");
const itemPath = `/trashbin/${client.username}/trash${normalizePath(args.trashPath)}`;
const restoreUrl = `${client.baseUrl}/trashbin/${client.username}/restore`;
try {
await client.move(itemPath, restoreUrl);
} catch (err: any) {
const status = err?.response?.status;
if (status === 404) {
return makeErrorResponse(`Trashbin item not found: ${args.trashPath}. It may have already been permanently deleted.`);
}
throw err;
}
return makeToolResponse({ success: true, restoredPath: args.trashPath });
}
async function handleTrashDelete(
args: { trashPath: string },
client: NextcloudClient
): Promise<ToolResponse> {
if (!args.trashPath) return makeErrorResponse("trashPath is required");
const itemPath = `/trashbin/${client.username}/trash${normalizePath(args.trashPath)}`;
try {
await client.delete(itemPath);
} catch (err: any) {
const status = err?.response?.status;
if (status === 404) {
return makeErrorResponse(`Trashbin item not found: ${args.trashPath}`);
}
throw err;
}
return makeToolResponse({ success: true });
}
async function handleTrashEmpty(
_args: Record<string, never>,
client: NextcloudClient
): Promise<ToolResponse> {
const trashPath = `/trashbin/${client.username}/trash`;
try {
await client.delete(trashPath);
} catch (err: any) {
const status = err?.response?.status;
if (status === 404) {
return makeErrorResponse("Trashbin is already empty");
}
throw err;
}
return makeToolResponse({ success: true });
}
// --- Step 8: Favorites & Versions ---
async function handleSetFavorite(
args: { path: string; favorite: boolean },
client: NextcloudClient
): Promise<ToolResponse> {
if (!args.path) return makeErrorResponse("path is required");
if (args.favorite === undefined) return makeErrorResponse("favorite is required");
const path = normalizePath(args.path);
const davPath = buildDavPath(client.username, path);
await client.proppatch(davPath, buildProppatchBody("oc", "favorite", args.favorite ? "1" : "0"));
// PROPFIND Depth:0 for updated metadata
const xml = await client.propfind(davPath, buildPropfindBody(), "0");
const meta = parsePropfindSingleFileResponse(xml, davPath);
return makeToolResponse(meta ?? { path, favorite: args.favorite });
}
async function handleGetFileVersions(
args: { fileId: number },
client: NextcloudClient
): Promise<ToolResponse> {
if (!args.fileId && args.fileId !== 0) return makeErrorResponse("fileId is required");
const versionsPath = `/versions/${client.username}/versions/${args.fileId}`;
try {
const xml = await client.propfind(versionsPath, buildVersionsPropfindBody(), "1");
const versions = parseVersionsResponse(xml);
return makeToolResponse(versions);
} catch (err: any) {
const status = err?.response?.status;
if (status === 404) {
return makeErrorResponse(`No versions found for file ID: ${args.fileId}`);
}
throw err;
}
}
async function handleRestoreFileVersion(
args: { fileId: number; versionName: string },
client: NextcloudClient
): Promise<ToolResponse> {
if (!args.fileId && args.fileId !== 0) return makeErrorResponse("fileId is required");
if (!args.versionName) return makeErrorResponse("versionName is required");
const versionPath = `/versions/${client.username}/versions/${args.fileId}/${args.versionName}`;
const restoreUrl = `${client.baseUrl}/versions/${client.username}/restore`;
try {
await client.move(versionPath, restoreUrl);
} catch (err: any) {
const status = err?.response?.status;
if (status === 404) {
return makeErrorResponse(`Version not found: ${args.versionName} for file ID ${args.fileId}`);
}
throw err;
}
return makeToolResponse({ success: true });
}
// --- Step 7: Trashbin ---
async function handleTrashList(
_args: Record<string, never>,
client: NextcloudClient
): Promise<ToolResponse> {
const trashPath = `/trashbin/${client.username}/trash`;
const xml = await client.propfind(trashPath, buildTrashbinPropfindBody(), "1");
const items = parseTrashbinResponse(xml);
return makeToolResponse(items);
}
async function handleTrashRestore(
args: { trashPath: string },
client: NextcloudClient
@@ -1382,3 +1521,63 @@ async function handleTrashEmpty(
return makeToolResponse({ success: true });
}
// --- Step 8: Favorites & Versions ---
async function handleSetFavorite(
args: { path: string; favorite: boolean },
client: NextcloudClient
): Promise<ToolResponse> {
if (!args.path) return makeErrorResponse("path is required");
if (args.favorite === undefined || args.favorite === null) return makeErrorResponse("favorite is required");
const path = normalizePath(args.path);
const davPath = buildDavPath(client.username, path);
// PROPPATCH to set oc:favorite
const value = args.favorite ? "1" : "0";
await client.proppatch(davPath, buildProppatchBody("oc", "favorite", value));
// PROPFIND Depth:0 to return updated metadata
const xml = await client.propfind(davPath, buildPropfindBody(), "0");
const meta = parsePropfindSingleFileResponse(xml, davPath);
return makeToolResponse(meta ?? { path, favorite: args.favorite });
}
async function handleGetFileVersions(
args: { fileId: number },
client: NextcloudClient
): Promise<ToolResponse> {
if (args.fileId === undefined || args.fileId === null) return makeErrorResponse("fileId is required");
const versionsPath = `/versions/${client.username}/versions/${args.fileId}`;
const xml = await client.propfind(versionsPath, buildVersionsPropfindBody(), "1");
const versions = parseVersionsResponse(xml);
return makeToolResponse(versions);
}
async function handleRestoreFileVersion(
args: { fileId: number; versionName: string },
client: NextcloudClient
): Promise<ToolResponse> {
if (args.fileId === undefined || args.fileId === null) return makeErrorResponse("fileId is required");
if (!args.versionName) return makeErrorResponse("versionName is required");
const sourcePath = `/versions/${client.username}/versions/${args.fileId}/${args.versionName}`;
const destination = `${client.baseUrl}/versions/${client.username}/restore`;
try {
await client.move(sourcePath, destination);
} catch (err: any) {
const status = err?.response?.status;
if (status === 404) {
return makeErrorResponse(`Version not found: fileId=${args.fileId}, version=${args.versionName}`);
}
throw err;
}
return makeToolResponse({ success: true, fileId: args.fileId, versionName: args.versionName });
}