mirror of
https://github.com/Nezumi-2711/onedrive-vercel-index.git
synced 2026-09-22 05:32:01 +00:00
add folders and file listing api
This commit is contained in:
+64
-16
@@ -1,25 +1,73 @@
|
||||
import axios from 'axios'
|
||||
import useSWR from 'swr'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { ParsedUrlQuery } from 'querystring'
|
||||
import Link from 'next/link'
|
||||
|
||||
const FileListing: FunctionComponent<{ query: ParsedUrlQuery }> = ({ query }) => {
|
||||
const { path } = query
|
||||
const humanFileSize = (size: number) => {
|
||||
if (size < 1024) return size + ' B'
|
||||
const i = Math.floor(Math.log(size) / Math.log(1024))
|
||||
const num = size / Math.pow(1024, i)
|
||||
const round = Math.round(num)
|
||||
const formatted = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round
|
||||
return `${formatted} ${'KMGTPEZY'[i - 1]}B`
|
||||
}
|
||||
|
||||
// Multiple levels of path
|
||||
if (Array.isArray(path)) {
|
||||
const encodePath = (path: string) => {
|
||||
const encodedPath = path === '/' ? '' : path
|
||||
if (encodedPath === '/' || encodedPath === '') {
|
||||
return ''
|
||||
}
|
||||
return encodedPath
|
||||
}
|
||||
|
||||
const fetcher = (url: string) => axios.get(url).then(res => res.data)
|
||||
|
||||
const FileListing: FunctionComponent<{ path: string }> = ({ path }) => {
|
||||
const { data, error } = useSWR(`/api?path=${path}`, fetcher)
|
||||
if (error) return <div>Failed to load</div>
|
||||
if (!data)
|
||||
return (
|
||||
<>
|
||||
{path.map((p: any, i: number) => (
|
||||
<div key="i">{p}</div>
|
||||
))}
|
||||
</>
|
||||
<div className="flex items-center justify-center bg-white shadow rounded py-32 space-x-1">
|
||||
<svg
|
||||
className="animate-spin -ml-1 mr-3 h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<div>Loading</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Only one level of path
|
||||
if (path) {
|
||||
return <div>{path}</div>
|
||||
}
|
||||
return <div>index page</div>
|
||||
const {
|
||||
files: { children },
|
||||
} = data
|
||||
return (
|
||||
<div className="bg-white shadow rounded">
|
||||
<div className="p-3 grid grid-cols-9 items-center space-x-2">
|
||||
<div className="col-span-6 font-bold">Name</div>
|
||||
<div className="font-bold col-span-2">Created</div>
|
||||
<div className="font-bold">Size</div>
|
||||
</div>
|
||||
{children.map((c: any) => (
|
||||
<Link href={`${encodePath(path)}/${c.name}`} key={c.id} passHref>
|
||||
<div className="p-3 grid grid-cols-9 items-center space-x-2 cursor-pointer hover:bg-gray-100">
|
||||
<div className="col-span-6 truncate">{c.name}</div>
|
||||
<div className="font-mono text-sm col-span-2">
|
||||
{new Date(c.createdDateTime).toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'short' })}
|
||||
</div>
|
||||
<div className="font-mono text-sm">{humanFileSize(c.size)}</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FileListing
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"clientId": "d87bcc39-1750-4ca0-ad54-f8d0efbb2735",
|
||||
"redirectUri": "http://localhost",
|
||||
"base": "/Public",
|
||||
"authApi": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
"driveApi": "https://graph.microsoft.com/v1.0/me/drive"
|
||||
}
|
||||
Generated
+29
-5906
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -13,7 +13,8 @@
|
||||
"next": "11.0.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-file-icon": "^1.0.0"
|
||||
"react-file-icon": "^1.0.0",
|
||||
"swr": "^0.5.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "17.0.11",
|
||||
|
||||
+7
-3
@@ -1,6 +1,6 @@
|
||||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
|
||||
import siteConfig from '../config/site.json'
|
||||
import Navbar from '../components/Navbar'
|
||||
@@ -8,7 +8,7 @@ import FileListing from '../components/FileListing'
|
||||
import Footer from '../components/Footer'
|
||||
|
||||
export default function Folders() {
|
||||
const { query } = useRouter()
|
||||
const { asPath } = useRouter()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
||||
@@ -19,7 +19,11 @@ export default function Folders() {
|
||||
<main className="flex flex-col w-full flex-1 bg-gray-50">
|
||||
<Navbar />
|
||||
<div className="mx-auto w-full max-w-4xl">
|
||||
<FileListing query={query} />
|
||||
<div className="py-3 text-sm text-gray-600">
|
||||
<Link href="/">🚩 Home </Link>
|
||||
{decodeURIComponent(asPath)}
|
||||
</div>
|
||||
<FileListing path={asPath} />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
type Data = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export default function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
res.status(200).json({ name: 'John Doe' })
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import axios from 'axios'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import apiConfig from '../../config/api.json'
|
||||
|
||||
const encodePath = (path: string) => {
|
||||
const encodedPath = `${apiConfig.base}${path === '/' ? '' : path}`
|
||||
if (encodedPath === '/' || encodedPath === '') {
|
||||
return ''
|
||||
}
|
||||
return encodeURIComponent(':' + encodedPath)
|
||||
}
|
||||
|
||||
// Store access token in memory, cuz Vercel doesn't provide key-value storage natively
|
||||
let _access_token = ''
|
||||
const getAccessToken = async () => {
|
||||
if (_access_token) {
|
||||
console.log('Fetch token from memory.')
|
||||
return _access_token
|
||||
}
|
||||
|
||||
const body = new URLSearchParams()
|
||||
body.append('client_id', apiConfig.clientId)
|
||||
body.append('redirect_uri', apiConfig.redirectUri)
|
||||
body.append('client_secret', process.env.CLIENT_SECRET ? process.env.CLIENT_SECRET : '')
|
||||
body.append('refresh_token', process.env.REFRESH_TOKEN ? process.env.REFRESH_TOKEN : '')
|
||||
body.append('grant_type', 'refresh_token')
|
||||
|
||||
const resp = await axios.post(apiConfig.authApi, body, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
})
|
||||
|
||||
if (resp.data.access_token) {
|
||||
_access_token = resp.data.access_token
|
||||
return _access_token
|
||||
}
|
||||
}
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { path = '/' } = req.query
|
||||
|
||||
if (typeof path === 'string') {
|
||||
const accessToken = await getAccessToken()
|
||||
const requestUrl = `${apiConfig.driveApi}/root${encodePath(path)}`
|
||||
const files = await axios.get(requestUrl, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
params: {
|
||||
select: 'name,size,id,createdDateTime',
|
||||
expand: 'children(select=name,createdDateTime,eTag,size,id,file)',
|
||||
},
|
||||
})
|
||||
|
||||
res.status(200).json({ path, files: files.data })
|
||||
return
|
||||
}
|
||||
|
||||
res.status(404).json({ error: 'Path query invalid.' })
|
||||
}
|
||||
+7
-4
@@ -1,6 +1,6 @@
|
||||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
|
||||
import siteConfig from '../config/site.json'
|
||||
import Navbar from '../components/Navbar'
|
||||
@@ -8,7 +8,7 @@ import FileListing from '../components/FileListing'
|
||||
import Footer from '../components/Footer'
|
||||
|
||||
export default function Home() {
|
||||
const { query } = useRouter()
|
||||
const { asPath } = useRouter()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
||||
@@ -18,8 +18,11 @@ export default function Home() {
|
||||
|
||||
<main className="flex flex-col w-full flex-1 bg-gray-50">
|
||||
<Navbar />
|
||||
<div className="mx-auto w-full max-w-4xl border-4 border-red-100">
|
||||
<FileListing query={query} />
|
||||
<div className="mx-auto w-full max-w-4xl">
|
||||
<div className="py-3 text-sm text-gray-600">
|
||||
<Link href="/">🚩 Home</Link>
|
||||
</div>
|
||||
<FileListing path={asPath} />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user