From d33096d3bf5090e4693c442752ec0f511a78211e Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Fri, 11 Aug 2023 14:53:49 +0200 Subject: [PATCH] fix(dav): Fix DAV functions to make work with them easier * `davGetClient` should default to the DAV remote URL instead of including the users root (to allow SEARCH) * `getFavoriteNodes` should work with any path * Add missing documentation Signed-off-by: Ferdinand Thiessen --- __tests__/dav/dav.spec.ts | 16 +++++++-------- lib/dav/dav.ts | 43 ++++++++++++++++++++++++++++----------- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/__tests__/dav/dav.spec.ts b/__tests__/dav/dav.spec.ts index 12738dee..41e3edbd 100644 --- a/__tests__/dav/dav.spec.ts +++ b/__tests__/dav/dav.spec.ts @@ -1,7 +1,7 @@ import { afterAll, describe, expect, test, vi } from 'vitest' -import { readFile } from 'fs/promises' +import { readFile } from 'node:fs/promises' -import { File, Folder, davDefaultRootUrl, davGetDefaultPropfind, davGetFavoritesReport, davRootPath, getFavoriteNodes } from '../../lib' +import { File, Folder, davRemoteURL, davGetFavoritesReport, davRootPath, getFavoriteNodes } from '../../lib' vi.mock('@nextcloud/auth') vi.mock('@nextcloud/router') @@ -15,8 +15,8 @@ describe('DAV functions', () => { expect(davRootPath).toBe('/files/test') }) - test('root url is correct', () => { - expect(davDefaultRootUrl).toBe('https://localhost/dav/files/test') + test('remote url is correct', () => { + expect(davRemoteURL).toBe('https://localhost/dav') }) }) @@ -39,7 +39,7 @@ describe('DAV requests', () => { const nodes = await getFavoriteNodes(client as never) // Check client was called correctly expect(client.getDirectoryContents).toBeCalled() - expect(client.getDirectoryContents.mock.lastCall?.at(0)).toBe('/') + expect(client.getDirectoryContents.mock.lastCall?.at(0)).toBe(`${davRootPath}/`) expect(client.getDirectoryContents.mock.lastCall?.at(1)?.data).toBe(davGetFavoritesReport()) expect(client.getDirectoryContents.mock.lastCall?.at(1)?.headers?.method).toBe('REPORT') // Check for correct output @@ -68,9 +68,9 @@ describe('DAV requests', () => { const nodes = await getFavoriteNodes(client as never, '/Neuer Ordner') // Check client was called correctly expect(client.getDirectoryContents).toBeCalled() - expect(client.getDirectoryContents.mock.lastCall?.at(0)).toBe('/Neuer Ordner') - expect(client.getDirectoryContents.mock.lastCall?.at(1)?.data).toBe(davGetDefaultPropfind()) - expect(client.getDirectoryContents.mock.lastCall?.at(1)?.headers?.method).toBe('PROPFIND') + expect(client.getDirectoryContents.mock.lastCall?.at(0)).toBe(`${davRootPath}/Neuer Ordner`) + expect(client.getDirectoryContents.mock.lastCall?.at(1)?.data).toBe(davGetFavoritesReport()) + expect(client.getDirectoryContents.mock.lastCall?.at(1)?.headers?.method).toBe('REPORT') // There are no inner nodes expect(nodes.length).toBe(0) }) diff --git a/lib/dav/dav.ts b/lib/dav/dav.ts index 1a8b0799..27843472 100644 --- a/lib/dav/dav.ts +++ b/lib/dav/dav.ts @@ -27,7 +27,7 @@ import { File } from '../files/file' import { Folder } from '../files/folder' import { NodeData } from '../files/nodeData' import { davParsePermissions } from './davPermissions' -import { davGetDefaultPropfind, davGetFavoritesReport } from './davProperties' +import { davGetFavoritesReport } from './davProperties' import { getCurrentUser, getRequestToken } from '@nextcloud/auth' import { generateRemoteUrl } from '@nextcloud/router' @@ -43,15 +43,22 @@ interface ResponseProps extends DAVResultResponseProps { size: number } +/** + * The DAV root path for the current user + */ export const davRootPath = `/files/${getCurrentUser()?.uid}` -export const davDefaultRootUrl = generateRemoteUrl('dav' + davRootPath) + +/** + * The DAV remote URL used as base URL for the WebDAV client + */ +export const davRemoteURL = generateRemoteUrl('dav') /** * Get a WebDAV client configured to include the Nextcloud request token * - * @param davURL The DAV root URL + * @param davURL The DAV remote URL */ -export const davGetClient = function(davURL = davDefaultRootUrl) { +export const davGetClient = function(davURL = davRemoteURL) { const client = createClient(davURL, { headers: { requesttoken: getRequestToken() || '', @@ -82,20 +89,32 @@ export const davGetClient = function(davURL = davDefaultRootUrl) { * * @param davClient The WebDAV client to use for performing the request * @param path Base path for the favorites, if unset all favorites are queried + * @param davRoot The root path for the DAV user (defaults to `davRootPath`) + * @example + * ```js + * import { davGetClient, davRootPath, getFavoriteNodes } from '@nextcloud/files' + * + * const client = davGetClient() + * // query favorites for the root + * const favorites = await getFavoriteNodes(client) + * // which is the same as writing: + * const favorites = await getFavoriteNodes(client, '/', davRootPath) + * ``` */ -export const getFavoriteNodes = async (davClient: WebDAVClient, path = '/') => { - const contentsResponse = await davClient.getDirectoryContents(path, { +export const getFavoriteNodes = async (davClient: WebDAVClient, path = '/', davRoot = davRootPath) => { + const contentsResponse = await davClient.getDirectoryContents(`${davRoot}${path}`, { details: true, - // Only filter favorites if we're at the root - data: path === '/' ? davGetFavoritesReport() : davGetDefaultPropfind(), + data: davGetFavoritesReport(), headers: { - // Patched in WebdavClient.ts - method: path === '/' ? 'REPORT' : 'PROPFIND', + // see davGetClient for patched webdav client + method: 'REPORT', }, includeSelf: true, }) as ResponseDataDetailed - return contentsResponse.data.filter(node => node.filename !== path).map((result) => davResultToNode(result)) + return contentsResponse.data + .filter(node => node.filename !== path) // exclude current dir + .map((result) => davResultToNode(result, davRoot)) } /** @@ -114,7 +133,7 @@ export const davResultToNode = function(node: FileStat, davRoot = davRootPath): source: generateRemoteUrl(`dav${davRoot}${node.filename}`), mtime: new Date(Date.parse(node.lastmod)), mime: node.mime as string, - size: (props?.size as number) || 0, + size: props?.size || Number.parseInt(props.getcontentlength || '0'), permissions, owner, root: davRoot,