add folders and file listing api

This commit is contained in:
spencerwooo
2021-06-23 00:15:19 +01:00
parent 1d64271aaa
commit bada403a9d
8 changed files with 176 additions and 5943 deletions
+64 -16
View File
@@ -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>
)
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>
)
}
// Only one level of path
if (path) {
return <div>{path}</div>
}
return <div>index page</div>
}
export default FileListing
+7
View File
@@ -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"
}
+29 -5906
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -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
View File
@@ -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>
-13
View File
@@ -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' })
}
+60
View File
@@ -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
View File
@@ -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>