Skip to content

Commit

Permalink
Merge pull request #741 from nextcloud-libraries/fix/result-to-node
Browse files Browse the repository at this point in the history
  • Loading branch information
skjnldsv committed Aug 23, 2023
2 parents ab973d7 + 352b98b commit 4b8ab1e
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 9 deletions.
51 changes: 51 additions & 0 deletions README.md
Expand Up @@ -2,3 +2,54 @@
[![npm last version](https://img.shields.io/npm/v/@nextcloud/files.svg?style=flat-square)](https://www.npmjs.com/package/@nextcloud/files) [![Code coverage](https://img.shields.io/codecov/c/github/nextcloud-libraries/nextcloud-files?style=flat-square)](https://app.codecov.io/gh/nextcloud-libraries/nextcloud-files) [![Project documentation](https://img.shields.io/badge/documentation-online-blue?style=flat-square)](https://nextcloud.github.io/nextcloud-files/)

Nextcloud Files helpers for Nextcloud apps and libraries

## Usage example

### Using WebDAV to query favorite nodes

```ts
import { davGetClient, davRootPath, getFavoriteNodes } from '@nextcloud/files'

const client = davGetClient()
// query favorites for the root folder (meaning all favorites)
const favorites = await getFavoriteNodes(client)
// which is the same as writing:
const favorites = await getFavoriteNodes(client, '/', davRootPath)
```

### Using WebDAV to list all nodes in directory

```ts
import {
davGetClient,
davGetDefaultPropfind,
davResultToNode,
davRootPath,
davRemoteURL
} from '@nextcloud/files'

// Get the DAV client for the default remote
const client = davGetClient()
// which is the same as writing
const client = davGetClient(davRemoteURL)
// of cause you can also configure another WebDAV remote
const client = davGetClient('https://example.com/dav')

const path = '/my-folder/' // the directory you want to list

// Query the directory content using the webdav library
// `davRootPath` is the files root, for Nextcloud this is '/files/USERID', by default the current user is used
const results = client.getDirectoryContents(`${davRootPath}${path}`, {
details: true,
// Query all required properties for a Node
data: davGetDefaultPropfind()
})

// Convert the result to an array of Node
const nodes = results.data.map((result) => davResultToNode(r))
// If you specified a different root in the `getDirectoryContents` you must add this also on the `davResultToNode` call:
const nodes = results.data.map((result) => davResultToNode(r, myRoot))
// Same if you used a different remote URL:
const nodes = results.data.map((result) => davResultToNode(r, myRoot, myRemoteURL))

```
60 changes: 59 additions & 1 deletion __tests__/dav/dav.spec.ts
@@ -1,7 +1,8 @@
import { afterAll, describe, expect, test, vi } from 'vitest'
import { readFile } from 'node:fs/promises'

import { File, Folder, davRemoteURL, davGetFavoritesReport, davRootPath, getFavoriteNodes } from '../../lib'
import { File, Folder, davRemoteURL, davGetFavoritesReport, davRootPath, getFavoriteNodes, davResultToNode } from '../../lib'
import { FileStat } from 'webdav'

vi.mock('@nextcloud/auth')
vi.mock('@nextcloud/router')
Expand All @@ -20,6 +21,63 @@ describe('DAV functions', () => {
})
})

describe('davResultToNode', () => {
/* Result of:
davGetClient().getDirectoryContents(`${davRootPath}${path}`, { details: true })
*/
const result: FileStat = {
filename: '/files/test/New folder/Neue Textdatei.md',
basename: 'Neue Textdatei.md',
lastmod: 'Tue, 25 Jul 2023 12:29:34 GMT',
size: 123,
type: 'file',
etag: '7a27142de0a62ed27a7293dbc16e93bc',
mime: 'text/markdown',
props: {
resourcetype: { collection: false },
displayname: 'New File',
getcontentlength: '123',
getcontenttype: 'text/markdown',
getetag: '"7a27142de0a62ed27a7293dbc16e93bc"',
getlastmodified: 'Tue, 25 Jul 2023 12:29:34 GMT',
},
}

test('path does not contain root', () => {
const node = davResultToNode(result)
expect(node.basename).toBe(result.basename)
expect(node.extension).toBe('.md')
expect(node.source).toBe('https://localhost/dav/files/test/New folder/Neue Textdatei.md')
expect(node.root).toBe(davRootPath)
expect(node.path).toBe('/New folder/Neue Textdatei.md')
expect(node.dirname).toBe('/New folder')
expect(node.size).toBe(123)
expect(node.mtime?.getTime()).toBe(Date.parse(result.lastmod))
expect(node.mime).toBe(result.mime)
})

test('has correct root set', () => {
const remoteResult = { ...result, filename: '/root/New folder/Neue Textdatei.md' }
const node = davResultToNode(remoteResult, '/root')
expect(node.basename).toBe(remoteResult.basename)
expect(node.extension).toBe('.md')
expect(node.root).toBe('/root')
expect(node.source).toBe('https://localhost/dav/root/New folder/Neue Textdatei.md')
expect(node.path).toBe('/New folder/Neue Textdatei.md')
expect(node.dirname).toBe('/New folder')
})

test('has correct remote path set', () => {
const remoteResult = { ...result, filename: '/root/New folder/Neue Textdatei.md' }
const node = davResultToNode(remoteResult, '/root', 'http://example.com/dav')
expect(node.basename).toBe(remoteResult.basename)
expect(node.extension).toBe('.md')
expect(node.source).toBe('http://example.com/dav/root/New folder/Neue Textdatei.md')
expect(node.path).toBe('/New folder/Neue Textdatei.md')
expect(node.dirname).toBe('/New folder')
})
})

describe('DAV requests', () => {
test('request all favorite files', async () => {
const favoritesResponseJSON = JSON.parse((await readFile(new URL('../fixtures/favorites-response.json', import.meta.url))).toString())
Expand Down
2 changes: 1 addition & 1 deletion __tests__/fixtures/favorites-response.json
@@ -1 +1 @@
[{"filename":"/Neuer Ordner","basename":"Neuer Ordner","lastmod":"Mon, 24 Jul 2023 16:30:44 GMT","size":0,"type":"directory","etag":"64bea734d3987","props":{"getetag":"\"64bea734d3987\"","getlastmodified":"Mon, 24 Jul 2023 16:30:44 GMT","quota-available-bytes":-3,"resourcetype":{"collection":""},"has-preview":false,"is-encrypted":0,"mount-type":"","share-attributes":"[]","comments-unread":0,"favorite":1,"fileid":74,"owner-display-name":"user1","owner-id":"user1","permissions":"RGDNVCK","share-types":{"share-type":3},"size":0,"share-permissions":31}},{"filename":"/New folder/Neue Textdatei.md","basename":"Neue Textdatei.md","lastmod":"Tue, 25 Jul 2023 12:29:34 GMT","size":0,"type":"file","etag":"7a27142de0a62ed27a7293dbc16e93bc","mime":"text/markdown","props":{"getcontentlength":0,"getcontenttype":"text/markdown","getetag":"\"7a27142de0a62ed27a7293dbc16e93bc\"","getlastmodified":"Tue, 25 Jul 2023 12:29:34 GMT","resourcetype":"","has-preview":false,"mount-type":"shared","share-attributes":"[{\"scope\":\"permissions\",\"key\":\"download\",\"enabled\":false}]","comments-unread":0,"favorite":1,"fileid":80,"owner-display-name":"admin","owner-id":"admin","permissions":"SRGDNVW","share-types":"","size":0,"share-permissions":19}}]
[{"filename":"/files/test/Neuer Ordner","basename":"Neuer Ordner","lastmod":"Mon, 24 Jul 2023 16:30:44 GMT","size":0,"type":"directory","etag":"64bea734d3987","props":{"getetag":"\"64bea734d3987\"","getlastmodified":"Mon, 24 Jul 2023 16:30:44 GMT","quota-available-bytes":-3,"resourcetype":{"collection":""},"has-preview":false,"is-encrypted":0,"mount-type":"","share-attributes":"[]","comments-unread":0,"favorite":1,"fileid":74,"owner-display-name":"user1","owner-id":"user1","permissions":"RGDNVCK","share-types":{"share-type":3},"size":0,"share-permissions":31}},{"filename":"/files/test/New folder/Neue Textdatei.md","basename":"Neue Textdatei.md","lastmod":"Tue, 25 Jul 2023 12:29:34 GMT","size":0,"type":"file","etag":"7a27142de0a62ed27a7293dbc16e93bc","mime":"text/markdown","props":{"getcontentlength":0,"getcontenttype":"text/markdown","getetag":"\"7a27142de0a62ed27a7293dbc16e93bc\"","getlastmodified":"Tue, 25 Jul 2023 12:29:34 GMT","resourcetype":"","has-preview":false,"mount-type":"shared","share-attributes":"[{\"scope\":\"permissions\",\"key\":\"download\",\"enabled\":false}]","comments-unread":0,"favorite":1,"fileid":80,"owner-display-name":"admin","owner-id":"admin","permissions":"SRGDNVW","share-types":"","size":0,"share-permissions":19}}]
15 changes: 8 additions & 7 deletions lib/dav/dav.ts
Expand Up @@ -56,10 +56,10 @@ export const davRemoteURL = generateRemoteUrl('dav')
/**
* Get a WebDAV client configured to include the Nextcloud request token
*
* @param davURL The DAV remote URL
* @param remoteURL The DAV server remote URL
*/
export const davGetClient = function(davURL = davRemoteURL) {
const client = createClient(davURL, {
export const davGetClient = function(remoteURL = davRemoteURL) {
const client = createClient(remoteURL, {
headers: {
requesttoken: getRequestToken() || '',
},
Expand Down Expand Up @@ -121,22 +121,23 @@ export const getFavoriteNodes = async (davClient: WebDAVClient, path = '/', davR
* Covert DAV result `FileStat` to `Node`
*
* @param node The DAV result
* @param davRoot The DAV root path
* @param filesRoot The DAV files root path
* @param remoteURL The DAV server remote URL (same as on `davGetClient`)
*/
export const davResultToNode = function(node: FileStat, davRoot = davRootPath): Node {
export const davResultToNode = function(node: FileStat, filesRoot = davRootPath, remoteURL = davRemoteURL): Node {
const props = node.props as ResponseProps
const permissions = davParsePermissions(props?.permissions)
const owner = getCurrentUser()?.uid as string

const nodeData: NodeData = {
id: (props?.fileid as number) || 0,
source: generateRemoteUrl(`dav${davRoot}${node.filename}`),
source: `${remoteURL}${node.filename}`,
mtime: new Date(Date.parse(node.lastmod)),
mime: node.mime as string,
size: props?.size || Number.parseInt(props.getcontentlength || '0'),
permissions,
owner,
root: davRoot,
root: filesRoot,
attributes: {
...node,
...props,
Expand Down

0 comments on commit 4b8ab1e

Please sign in to comment.