diff --git a/components/SearchModal.tsx b/components/SearchModal.tsx index b0bd5db..c04a7cc 100644 --- a/components/SearchModal.tsx +++ b/components/SearchModal.tsx @@ -10,14 +10,27 @@ import Link from 'next/link' import Image from 'next/image' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -const useDriveItemSearch = () => { +import { OdSearchResult } from '../types' +import { getFileIcon } from '../utils/getFileIcon' +import siteConfig from '../config/site.json' +import { LoadingIcon } from './Loading' + +function useDriveItemSearch() { const [query, setQuery] = useState('') const searchDriveItem = async (q: string) => { - // TODO: currently using mock data, change this to a query to OneDrive API - const result = await axios.get('https://jsonplaceholder.typicode.com/posts') - console.log(q, result.data) + const { data } = await axios.get(`/api/search?q=${q}`) - return result.data + // Extract the searched item's path and convert it to the absolute path in onedrive-vercel-index + function mapAbsolutePath(path: string): string { + return siteConfig.baseDirectory === '/' ? path.split('root:')[1] : path.split(siteConfig.baseDirectory)[1] + } + + // Map parentReference to the absolute path of the search result + data.map(item => { + item['path'] = `${mapAbsolutePath(item.parentReference.path)}/${encodeURIComponent(item.name)}` + }) + + return data } const debouncedNotionSearch = useConstant(() => AwesomeDebouncePromise(searchDriveItem, 1000)) @@ -36,10 +49,13 @@ const useDriveItemSearch = () => { } } -const SearchModal: FC<{ +function SearchModal({ + searchOpen, + setSearchOpen, +}: { searchOpen: boolean setSearchOpen: Dispatch> -}> = ({ searchOpen, setSearchOpen }) => { +}) { const closeSearchBox = () => setSearchOpen(false) const { query, setQuery, results } = useDriveItemSearch() @@ -70,48 +86,50 @@ const SearchModal: FC<{ leaveTo="opacity-0 scale-95" >
- -
- -
+ + setQuery(e.target.value)} /> +
ESC
{results.loading && ( -
-
- purr loading -
-
Loading ...
+
+ + Loading ...
)} {results.error && ( -
- errored out -
Error: {results.error.message}
-
+
Error: {results.error.message}
)} {results.result && ( <> {results.result.length === 0 ? ( -
- empty list -
Nothing here...
-
+
Nothing here.
) : ( - results.result.map((result: any, i: number) => ( - -
-
{result.title}
-
{result.body}
+ results.result.map(result => ( + +
+ +
+
{result.name}
+
+ {decodeURIComponent(result.path)} +
+
)) diff --git a/pages/api/index.ts b/pages/api/index.ts index 025bc83..5547def 100644 --- a/pages/api/index.ts +++ b/pages/api/index.ts @@ -12,7 +12,7 @@ import { getOdAuthTokens, storeOdAuthTokens } from '../../utils/odAuthTokenStore const basePath = pathPosix.resolve('/', siteConfig.baseDirectory) const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret) -const encodePath = (path: string) => { +export function encodePath(path: string): string { let encodedPath = pathPosix.join(basePath, pathPosix.resolve('/', path)) if (encodedPath === '/' || encodedPath === '') { return '' @@ -21,7 +21,7 @@ const encodePath = (path: string) => { return `:${encodeURIComponent(encodedPath)}` } -async function getAccessToken(): Promise { +export async function getAccessToken(): Promise { const { accessToken, refreshToken } = await getOdAuthTokens() // Return in storage access token if it is still valid diff --git a/pages/api/search.ts b/pages/api/search.ts new file mode 100644 index 0000000..61e1853 --- /dev/null +++ b/pages/api/search.ts @@ -0,0 +1,36 @@ +import axios from 'axios' +import type { NextApiRequest, NextApiResponse } from 'next' + +import { encodePath, getAccessToken } from '.' +import apiConfig from '../../config/api.json' +import siteConfig from '../../config/site.json' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + // Get access token from storage + const accessToken = await getAccessToken() + + // Query parameter from request + 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)}')` + + try { + const { data } = await axios.get(searchApi, { + headers: { Authorization: `Bearer ${accessToken}` }, + params: { + select: 'id,name,file,folder,parentReference', + top: '10', + }, + }) + res.status(200).json(data.value) + } catch (error: any) { + res.status(error.response.status).json({ error: error.response.data }) + } + } else { + res.status(200).json([]) + } + return +} diff --git a/types/index.d.ts b/types/index.d.ts index 827a55e..f93f54d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -13,3 +13,34 @@ export type OdFileObject = { } } } + +export type OdFolderObject = { + '@odata.count': number + value: Array<{ + id: string + name: string + lastModifiedDateTime: string + size: number + folder: { + childCount: number + view: { + sortBy: 'name' + sortOrder: 'ascending' + viewType: 'thumbnails' + } + } + }> +} + +export type OdSearchResult = Array<{ + id: string + name: string + file?: OdFileObject + folder?: OdFolderObject + path: string + parentReference: { + id: string + name: string + path: string + } +}>