aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files/src/services/WebDavSearch.ts
blob: feb7f30b35768119d803dd8f75e2164700ff2983 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/*!
 * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

import type { INode } from '@nextcloud/files'
import type { ResponseDataDetailed, SearchResult } from 'webdav'

import { getCurrentUser } from '@nextcloud/auth'
import { defaultRootPath, getDavNameSpaces, getDavProperties, resultToNode } from '@nextcloud/files/dav'
import { getBaseUrl } from '@nextcloud/router'
import { client } from './WebdavClient.ts'
import logger from '../logger.ts'

export interface SearchNodesOptions {
	dir?: string,
	signal?: AbortSignal
}

/**
 * Search for nodes matching the given query.
 *
 * @param query - Search query
 * @param options - Options
 * @param options.dir - The base directory to scope the search to
 * @param options.signal - Abort signal for the request
 */
export async function searchNodes(query: string, { dir, signal }: SearchNodesOptions): Promise<INode[]> {
	const user = getCurrentUser()
	if (!user) {
		// the search plugin only works for user roots
		return []
	}

	query = query.trim()
	if (query.length < 3) {
		// the search plugin only works with queries of at least 3 characters
		return []
	}

	if (dir && !dir.startsWith('/')) {
		dir = `/${dir}`
	}

	logger.debug('Searching for nodes', { query, dir })
	const { data } = await client.search('/', {
		details: true,
		signal,
		data: `
<d:searchrequest ${getDavNameSpaces()}>
	 <d:basicsearch>
		 <d:select>
			 <d:prop>
			 ${getDavProperties()}
			 </d:prop>
		 </d:select>
		 <d:from>
			 <d:scope>
				 <d:href>/files/${user.uid}${dir || ''}</d:href>
				 <d:depth>infinity</d:depth>
			 </d:scope>
		 </d:from>
		 <d:where>
			 <d:like>
				 <d:prop>
					 <d:displayname/>
				 </d:prop>
				 <d:literal>%${query.replace('%', '')}%</d:literal>
			 </d:like>
		 </d:where>
		 <d:orderby/>
	</d:basicsearch>
</d:searchrequest>`,
	}) as ResponseDataDetailed<SearchResult>

	// check if the request was aborted
	if (signal?.aborted) {
		return []
	}

	// otherwise return the result mapped to Nextcloud nodes
	return data.results.map((result) => resultToNode(result, defaultRootPath, getBaseUrl()))
}