diff --git a/components/SearchModal.tsx b/components/SearchModal.tsx index 38aa27f..8c0f951 100644 --- a/components/SearchModal.tsx +++ b/components/SearchModal.tsx @@ -25,14 +25,12 @@ function useDriveItemSearch() { // Map parentReference to the absolute path of the search result data.map(item => { - // TODO: supporting sharepoint search where the path is not returned in parentReference - if ('path' in item.parentReference) { - item['path'] = `${mapAbsolutePath(item.parentReference.path)}/${encodeURIComponent(item.name)}` - } else { - throw Error( - 'We currently only support search in OneDrive international. SharePoint instances are not supported yet. See issue: https://github.com/spencerwooo/onedrive-vercel-index/issues/299' - ) - } + item['path'] = + 'path' in item.parentReference + ? // OneDrive International have the path returned in the parentReference field + `${mapAbsolutePath(item.parentReference.path)}/${encodeURIComponent(item.name)}` + : // OneDrive for Business/Education does not, so we need extra steps here + '' }) return data diff --git a/pages/api/index.ts b/pages/api/index.ts index 5547def..ef98722 100644 --- a/pages/api/index.ts +++ b/pages/api/index.ts @@ -12,6 +12,12 @@ import { getOdAuthTokens, storeOdAuthTokens } from '../../utils/odAuthTokenStore const basePath = pathPosix.resolve('/', siteConfig.baseDirectory) const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret) +/** + * Encode the path of the file relative to the base directory + * + * @param path Relative path of the file to the base directory + * @returns Absolute path of the file inside OneDrive + */ export function encodePath(path: string): string { let encodedPath = pathPosix.join(basePath, pathPosix.resolve('/', path)) if (encodedPath === '/' || encodedPath === '') { @@ -21,6 +27,11 @@ export function encodePath(path: string): string { return `:${encodeURIComponent(encodedPath)}` } +/** + * Fetch the access token from Redis storage and check if the token requires a renew + * + * @returns Access token for OneDrive API + */ export async function getAccessToken(): Promise { const { accessToken, refreshToken } = await getOdAuthTokens() diff --git a/pages/api/item.ts b/pages/api/item.ts new file mode 100644 index 0000000..9ae9176 --- /dev/null +++ b/pages/api/item.ts @@ -0,0 +1,32 @@ +import axios from 'axios' +import type { NextApiRequest, NextApiResponse } from 'next' + +import { encodePath, getAccessToken } from '.' +import apiConfig from '../../config/api.json' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + // Get access token from storage + const accessToken = await getAccessToken() + + // Get item details (specifically, its path) by its unique ID in OneDrive + const { id = '' } = req.query + + if (typeof id === 'string') { + const itemApi = `${apiConfig.driveApi}/items/${id}` + + try { + const { data } = await axios.get(itemApi, { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { + select: 'id,name,parentReference', + }, + }) + res.status(200).json(data) + } catch (error: any) { + res.status(error.response.status).json({ error: error.response.data }) + } + } else { + res.status(400).json({ error: 'Invalid driveItem ID.' }) + } + return +} diff --git a/pages/api/search.ts b/pages/api/search.ts index 1a9cff6..b626691 100644 --- a/pages/api/search.ts +++ b/pages/api/search.ts @@ -3,6 +3,18 @@ import type { NextApiRequest, NextApiResponse } from 'next' import { encodePath, getAccessToken } from '.' import apiConfig from '../../config/api.json' +import siteConfig from '../../config/site.json' + +/** + * Sanitize the search query + * + * @param query User search query, which may contain special characters + * @returns Sanitised query string which replaces non-alphanumeric characters with ' ' + */ +function sanitiseQuery(query: string): string { + const sanitisedQuery = query.replace(/[^a-zA-Z0-9]/g, ' ') + return encodeURIComponent(sanitisedQuery) +} export default async function handler(req: NextApiRequest, res: NextApiResponse) { // Get access token from storage @@ -12,15 +24,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const { q: searchQuery = '' } = req.query if (typeof searchQuery === 'string') { - // Construct Microsoft Graph Search API URL, and perform search only under the base dir - const encodedPath = encodePath('/') === '' ? encodePath('/') : encodePath('/') + ':' - const searchApi = `${apiConfig.driveApi}/root${encodedPath}/search(q='${encodeURIComponent(searchQuery)}')` + // Construct Microsoft Graph Search API URL, and perform search only under the base directory + const searchRootPath = encodePath('/') + const encodedPath = searchRootPath === '' ? searchRootPath : searchRootPath + ':' + + const searchApi = `${apiConfig.driveApi}/root${encodedPath}/search(q='${sanitiseQuery(searchQuery)}')` try { const { data } = await axios.get(searchApi, { headers: { Authorization: `Bearer ${accessToken}` }, params: { select: 'id,name,file,folder,parentReference', + top: siteConfig.maxItems, }, }) res.status(200).json(data.value)