diff --git a/.gitignore b/.gitignore index 84e2806..bfc7379 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ next-env.d.ts /docs /.next /.vscode +/src/pages/api/legacy/ diff --git a/README.md b/README.md index 8bf46ea..71d769d 100644 --- a/README.md +++ b/README.md @@ -29,31 +29,6 @@ For free plan, Google Drive offering 15GB of storage, and Onedrive offering 5GB I know there are a lot of people selling cheap edu account for Google Drive and Onedrive, but most of the time those account doesn't last long, especially Google Drive account. So I want to do it legit without buying cheap edu account or anything similar this time. -## Features -- Share files without any hassle -- Preview files before downloading -- Render readme file inside the folder -- Protect your folder with password -- Search for files easily -- Direct view or download files - - Can be used for hosting assets for your website - -## How to use -### Prerequisites -- Google Drive account -- Google Drive API key -- Vercel account - -### Setup -1. Fork [gudora-index/mbaharip](https://github.com/mbaharip/gudora-index) repository -2. Open [Setup page](https://drive.mbaharip.com/setup) and follow the instructions -3. Change the `site.config.js` to your liking -4. Deploy your forked repository to Vercel -5. Open your Vercel project, and go to **Settings > Environment Variables** -6. Add `ENCRYPTION_KEY`, `JWT_KEY`, and `NEXT_PUBLIC_DOMAIN` environment variables -7. Redeploy your project -8. Done! - ## Credits - [onedrive-vercel-index](https://github.com/spencerwooo/onedrive-vercel-index) by SpencerWooo for the inspiration and also some file type helpers. @@ -61,9 +36,17 @@ I know there are a lot of people selling cheap edu account for Google Drive and ## Known Issues - Fetching files from Google Drive API take too much time, it triggers 504 Error on Vercel. -- Can't download big files, because of Vercel's 10MB limit for serverless function. +- ~~Can't download big files, because of Vercel's 4.5MB limit for serverless function.~~ (Implement redirect to Google Drive download link instead of using Vercel serverless function for files bigger than 4MB) + - Need to set the folder to public, so it can be downloaded directly from Google Drive. + - The way to get files are changed because of this, now it's using the file name, and 8 first character of file id for hiding the id so no one can access it via Google Drive. + - All id and webContentLink are now encrypted using AES-128-CBC. +- Protected folder cause long response time, because it need to fetch the file / folder first > fetch each of the parents till root > check if it's inside protected folder > then checking the password. +- Download might show scan warning, because it's using Google Drive's direct download link. ## TODO +- [ ] Aim for max 1.5s response time for every API routes + - Fetch all routes - 600~900ms + - Fetch file or folder - - [x] Navigate through folders - [ ] File preview - [x] Audio @@ -83,4 +66,4 @@ I know there are a lot of people selling cheap edu account for Google Drive and - [x] Password protection - [x] Create token hash for sharing protected ~~files that valid for x hours~~ - [ ] Implement time limit for token -- [ ] ~~Pretty path URL~~ (Not possible, since Google Drive allowing multiple files with same name) \ No newline at end of file +- [x] ~~Pretty path URL~~ ~~(Not possible, since Google Drive allowing multiple files with same name)~~ (Kinda pretty now, it consist of the file name, then 8 character of file id for security) \ No newline at end of file diff --git a/src/components/Breadcrumb/index.tsx b/src/components/Breadcrumb/index.tsx index cb11175..66e5b4b 100644 --- a/src/components/Breadcrumb/index.tsx +++ b/src/components/Breadcrumb/index.tsx @@ -1,9 +1,9 @@ -import { TFileParent } from "@/types/googleapis"; +import { TFileParent } from "types/googleapis"; import Link from "next/link"; import { Fragment, useEffect, useState } from "react"; import { MdHome } from "react-icons/md"; import ReactLoading from "react-loading"; -import config from "@config/site.config"; +import config from "config/site.config"; type Props = { data: TFileParent[]; diff --git a/src/components/File/Grid.tsx b/src/components/File/Grid.tsx index 813eae9..2a687a7 100644 --- a/src/components/File/Grid.tsx +++ b/src/components/File/Grid.tsx @@ -1,8 +1,8 @@ -import { formatDuration } from "@/utils/formatHelper"; +import { formatDuration } from "utils/formatHelper"; import { drive_v3 } from "googleapis"; import Link from "next/link"; import { MdPlayCircleFilled } from "react-icons/md"; -import { getFileIcon } from "@utils/mimeTypesHelper"; +import { getFileIcon } from "utils/mimeTypesHelper"; import { BsFolderFill } from "react-icons/bs"; type Props = { diff --git a/src/components/File/List.tsx b/src/components/File/List.tsx index 59741fc..2fb2179 100644 --- a/src/components/File/List.tsx +++ b/src/components/File/List.tsx @@ -1,9 +1,9 @@ import { drive_v3 } from "googleapis"; import Link from "next/link"; -import { formatBytes, formatDate } from "@utils/formatHelper"; +import { formatBytes, formatDate } from "utils/formatHelper"; import { toast } from "react-toastify"; import { MdContentCopy, MdDownload } from "react-icons/md"; -import { getFileIcon } from "@utils/mimeTypesHelper"; +import { getFileIcon } from "utils/mimeTypesHelper"; import { BsFolderFill } from "react-icons/bs"; type Props = { diff --git a/src/components/FilePreview/AudioPreview/index.tsx b/src/components/FilePreview/AudioPreview/index.tsx index 13acc73..b6d4f6e 100644 --- a/src/components/FilePreview/AudioPreview/index.tsx +++ b/src/components/FilePreview/AudioPreview/index.tsx @@ -1,9 +1,9 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; import { useEffect, useState } from "react"; -import config from "@config/site.config"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; +import config from "config/site.config"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; import H5AudioPlayer from "react-h5-audio-player"; import "react-h5-audio-player/lib/styles.css"; diff --git a/src/components/FilePreview/CodePreview/index.tsx b/src/components/FilePreview/CodePreview/index.tsx index 27e4426..dbf37cd 100644 --- a/src/components/FilePreview/CodePreview/index.tsx +++ b/src/components/FilePreview/CodePreview/index.tsx @@ -1,12 +1,12 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; import { useEffect, useState } from "react"; import useSWR from "swr"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; -import MarkdownRender from "@components/utility/MarkdownRender"; -import fetcher from "@utils/swrFetch"; -import { getCodeLanguage } from "@utils/mimeTypesHelper"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; +import MarkdownRender from "components/utility/MarkdownRender"; +import fetcher from "utils/swrFetch"; +import { getCodeLanguage } from "utils/mimeTypesHelper"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/FilePreview/DefaultPreview/index.tsx b/src/components/FilePreview/DefaultPreview/index.tsx index 8c2f19f..952472f 100644 --- a/src/components/FilePreview/DefaultPreview/index.tsx +++ b/src/components/FilePreview/DefaultPreview/index.tsx @@ -1,6 +1,6 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; -import ErrorFeedback from "@components/APIFeedback/Error"; +import ErrorFeedback from "components/APIFeedback/Error"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/FilePreview/ImagePreview/index.tsx b/src/components/FilePreview/ImagePreview/index.tsx index e24282a..3f99b1b 100644 --- a/src/components/FilePreview/ImagePreview/index.tsx +++ b/src/components/FilePreview/ImagePreview/index.tsx @@ -1,10 +1,10 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; import { useEffect, useState } from "react"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; -import config from "@config/site.config"; -import { reverseString } from "@utils/hashHelper"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; +import config from "config/site.config"; +import { reverseString } from "utils/hashHelper"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/FilePreview/MarkdownPreview/index.tsx b/src/components/FilePreview/MarkdownPreview/index.tsx index d65d4b4..0d6b09f 100644 --- a/src/components/FilePreview/MarkdownPreview/index.tsx +++ b/src/components/FilePreview/MarkdownPreview/index.tsx @@ -1,10 +1,10 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; import useSWR from "swr"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; -import MarkdownRender from "@components/utility/MarkdownRender"; -import fetcher from "@utils/swrFetch"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; +import MarkdownRender from "components/utility/MarkdownRender"; +import fetcher from "utils/swrFetch"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/FilePreview/ModelPreview/index.tsx b/src/components/FilePreview/ModelPreview/index.tsx index 67bbb66..cbda2ca 100644 --- a/src/components/FilePreview/ModelPreview/index.tsx +++ b/src/components/FilePreview/ModelPreview/index.tsx @@ -1,11 +1,11 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; import { Canvas, useFrame, useLoader } from "@react-three/fiber"; -import { get3DLoader } from "@utils/mimeTypesHelper"; +import { get3DLoader } from "utils/mimeTypesHelper"; import { ReactNode, Suspense, useRef } from "react"; import { BufferGeometry } from "three"; -import ErrorFeedback from "@components/APIFeedback/Error"; -import { Stage, OrbitControls } from "@react-three/drei"; +import ErrorFeedback from "components/APIFeedback/Error"; +import { OrbitControls, Stage } from "@react-three/drei"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/FilePreview/OfficePreview/index.tsx b/src/components/FilePreview/OfficePreview/index.tsx index 90c2d48..3f8b9fb 100644 --- a/src/components/FilePreview/OfficePreview/index.tsx +++ b/src/components/FilePreview/OfficePreview/index.tsx @@ -1,4 +1,4 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; type Props = { diff --git a/src/components/FilePreview/PDFPreview/index.tsx b/src/components/FilePreview/PDFPreview/index.tsx index 6acb9b9..984c555 100644 --- a/src/components/FilePreview/PDFPreview/index.tsx +++ b/src/components/FilePreview/PDFPreview/index.tsx @@ -1,9 +1,9 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; import { useState } from "react"; -import config from "@config/site.config"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; +import config from "config/site.config"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/FilePreview/TextPreview/index.tsx b/src/components/FilePreview/TextPreview/index.tsx index 0da43ac..eca127e 100644 --- a/src/components/FilePreview/TextPreview/index.tsx +++ b/src/components/FilePreview/TextPreview/index.tsx @@ -1,10 +1,10 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; import useSWR from "swr"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; -import MarkdownRender from "@components/utility/MarkdownRender"; -import fetcher from "@utils/swrFetch"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; +import MarkdownRender from "components/utility/MarkdownRender"; +import fetcher from "utils/swrFetch"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/FilePreview/VideoPreview/index.tsx b/src/components/FilePreview/VideoPreview/index.tsx index d72573d..d1fc40a 100644 --- a/src/components/FilePreview/VideoPreview/index.tsx +++ b/src/components/FilePreview/VideoPreview/index.tsx @@ -1,9 +1,9 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; import ReactPlayer from "react-player"; import { useState } from "react"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/layout/FileDetails/DetailsButtons.tsx b/src/components/layout/FileDetails/DetailsButtons.tsx index be289c0..c11dd9d 100644 --- a/src/components/layout/FileDetails/DetailsButtons.tsx +++ b/src/components/layout/FileDetails/DetailsButtons.tsx @@ -1,10 +1,10 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; import { MdCopyAll, MdDownload } from "react-icons/md"; import Link from "next/link"; -import config from "@config/site.config"; +import config from "config/site.config"; import { useEffect, useState } from "react"; -import useCopyText from "@hooks/useCopyText"; +import useCopyText from "hooks/useCopyText"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/layout/FileDetails/index.tsx b/src/components/layout/FileDetails/index.tsx index 9a83f49..1a61121 100644 --- a/src/components/layout/FileDetails/index.tsx +++ b/src/components/layout/FileDetails/index.tsx @@ -1,10 +1,10 @@ -import { TFile } from "@/types/googleapis"; +import { TFile } from "types/googleapis"; import { drive_v3 } from "googleapis"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import { formatBytes, formatDate, formatDuration } from "@utils/formatHelper"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import { formatBytes, formatDate, formatDuration } from "utils/formatHelper"; import { useEffect, useState } from "react"; -import DetailsButtons from "@components/layout/FileDetails/DetailsButtons"; -import { getFilePreview } from "@utils/mimeTypesHelper"; +import DetailsButtons from "components/layout/FileDetails/DetailsButtons"; +import { getFilePreview } from "utils/mimeTypesHelper"; type Props = { data: TFile | drive_v3.Schema$File; diff --git a/src/components/layout/Files/GridLayout.tsx b/src/components/layout/Files/GridLayout.tsx index 74f1111..1337412 100644 --- a/src/components/layout/Files/GridLayout.tsx +++ b/src/components/layout/Files/GridLayout.tsx @@ -1,7 +1,7 @@ -import { FilesResponse } from "@/types/googleapis"; -import GridFile from "@components/File/Grid"; +import { FilesResponse } from "types/googleapis"; +import GridFile from "components/File/Grid"; import { drive_v3 } from "googleapis"; -import EmptyFeedback from "@components/APIFeedback/Empty"; +import EmptyFeedback from "components/APIFeedback/Empty"; type Props = { data: FilesResponse | undefined; diff --git a/src/components/layout/Files/ListLayout.tsx b/src/components/layout/Files/ListLayout.tsx index 6722100..0fcd76e 100644 --- a/src/components/layout/Files/ListLayout.tsx +++ b/src/components/layout/Files/ListLayout.tsx @@ -1,7 +1,7 @@ -import { FilesResponse } from "@/types/googleapis"; -import ListFile from "@components/File/List"; +import { FilesResponse } from "types/googleapis"; +import ListFile from "components/File/List"; import { drive_v3 } from "googleapis"; -import EmptyFeedback from "@components/APIFeedback/Empty"; +import EmptyFeedback from "components/APIFeedback/Empty"; type Props = { data: FilesResponse | undefined; diff --git a/src/components/layout/Footer/index.tsx b/src/components/layout/Footer/index.tsx index 5445c9e..e1e8480 100644 --- a/src/components/layout/Footer/index.tsx +++ b/src/components/layout/Footer/index.tsx @@ -1,4 +1,4 @@ -import siteConfig from "@config/site.config"; +import siteConfig from "config/site.config"; export default function Footer() { const currentYear = new Date().getFullYear(); diff --git a/src/components/layout/Navbar/index.tsx b/src/components/layout/Navbar/index.tsx index c36eedb..9e2fa26 100644 --- a/src/components/layout/Navbar/index.tsx +++ b/src/components/layout/Navbar/index.tsx @@ -1,26 +1,26 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import siteConfig from "@config/site.config"; -import useLocalStorage from "@/hooks/useLocalStorage"; +import siteConfig from "config/site.config"; +import useLocalStorage from "hooks/useLocalStorage"; import Link from "next/link"; import Image from "next/image"; import { - MdLightMode, - MdDarkMode, - MdSearch, - MdMenu, MdClose, + MdDarkMode, + MdLightMode, + MdMenu, + MdSearch, } from "react-icons/md"; -import Modal from "@/components/utility/Modal"; -import { formatBytes } from "@/utils/formatHelper"; -import { ErrorResponse, SearchResponse } from "@/types/googleapis"; +import Modal from "components/utility/Modal"; +import { formatBytes } from "utils/formatHelper"; +import { ErrorResponse, SearchResponse } from "types/googleapis"; import useSWR from "swr"; -import fetcher from "@utils/swrFetch"; +import fetcher from "utils/swrFetch"; import { drive_v3 } from "googleapis"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; -import EmptyFeedback from "@components/APIFeedback/Empty"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; +import EmptyFeedback from "components/APIFeedback/Empty"; import { BsFolderFill } from "react-icons/bs"; -import { getFileIcon } from "@utils/mimeTypesHelper"; +import { getFileIcon } from "utils/mimeTypesHelper"; export default function Navbar() { const [isDarkMode, setIsDarkMode] = useLocalStorage("isDarkMode", "false"); diff --git a/src/components/layout/Password/index.tsx b/src/components/layout/Password/index.tsx index 3b87893..954b7d4 100644 --- a/src/components/layout/Password/index.tsx +++ b/src/components/layout/Password/index.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; -import useLocalStorage from "@hooks/useLocalStorage"; +import useLocalStorage from "hooks/useLocalStorage"; import { IoMdEye, IoMdEyeOff } from "react-icons/io"; import { MdLock } from "react-icons/md"; -import { hashToken } from "@utils/hashHelper"; +import { hashToken } from "utils/hashHelper"; type Props = { folderId: string; diff --git a/src/components/utility/MarkdownRender.tsx b/src/components/utility/MarkdownRender.tsx index c779792..6dd6e2b 100644 --- a/src/components/utility/MarkdownRender.tsx +++ b/src/components/utility/MarkdownRender.tsx @@ -8,7 +8,7 @@ import rehypeRaw from "rehype-raw"; import rehypePrism from "rehype-prism-plus"; import { SpecialComponents } from "react-markdown/lib/ast-to-react"; import { NormalComponents } from "react-markdown/lib/complex-types"; -import config from "@/config/site.config"; +import config from "config/site.config"; type Props = { content: string; diff --git a/src/components/utility/SwitchLayout.tsx b/src/components/utility/SwitchLayout.tsx index 4b8ab8f..97cf7f8 100644 --- a/src/components/utility/SwitchLayout.tsx +++ b/src/components/utility/SwitchLayout.tsx @@ -1,4 +1,4 @@ -import useLocalStorage from "@hooks/useLocalStorage"; +import useLocalStorage from "hooks/useLocalStorage"; import { MdArrowDropDown, MdGridView, MdList } from "react-icons/md"; import { useEffect, useRef, useState } from "react"; diff --git a/src/config/api.config.js b/src/config/api.config.js index b15843b..65d51d2 100644 --- a/src/config/api.config.js +++ b/src/config/api.config.js @@ -8,22 +8,24 @@ module.exports = { client_secret: "54f0505d11a8fe04d22ec642cdde4728:a6f911c70b0305a51160a2133a9bd2ada3c120567857002311f17bf29cc69e07d7f1a9ca20fd119d684191aee6e3866a", refresh_token: - "b6ef7852ce97441fb6a43e1701b788f1:d96b5bc1624455536b0f5934a1d39e223d11bacfb2edcbfe69be568c7230bec69d741bc4c789d5c435bf575ab70dfd9a4c350c70562b73ec4f6e4903f0aef825fdd2dd2b1b8c42cb78f0fed1d74be7785e9a48082aac76b23cb3dc419ce11e9a8c61a586423913a1b7c2d996a5306816", + "44078de6e3407c6dc6a41c65a5f58687:da00ab1cff6daea563ed1991da94fb4ac232dd09eb4a65286b2400cb3e36b5ba28a553b5e3a08bc05029839b7f6f83779e1feaecbe56c9aabce9dd245b2f40982818ac216be21c1409919c0c7a2f9be9915825e1d4708089b8ab59599d327704910f71e081851748b98f6d6e82ab8c30", files: { // How many files to show per page - itemsPerPage: 500, + itemsPerPage: 50, // Max number of files to show in search result searchResult: 10, // Starting point of the drive // Use 'root' to use My Drive as starting point // Or use folder id to use a specific folder as starting point // TODO: Change when final - rootFolder: "1KgPV6QB1GYT8fmn2uTfbtr9rDXqcRR0j", // Test folder + rootFolder: "root", + // rootFolder: "1KgPV6QB1GYT8fmn2uTfbtr9rDXqcRR0j", // Test folder }, // Cache control header // https://web.dev/uses-long-cache-ttl/ + // Default: 1 minute cache: "s-maxage=60, stale-while-revalidate", // Max response body size diff --git a/src/config/site.config.js b/src/config/site.config.js index b4b0c56..0f20997 100644 --- a/src/config/site.config.js +++ b/src/config/site.config.js @@ -1,4 +1,4 @@ -import { BsGithub, BsDiscord, BsPaypal } from "react-icons/bs"; +import { BsDiscord, BsGithub, BsPaypal } from "react-icons/bs"; const config = { /* Site MetaData */ diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts index d52a4be..b82a09e 100644 --- a/src/hooks/useLocalStorage.ts +++ b/src/hooks/useLocalStorage.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; type SetValue = (value: T | ((val: T) => T)) => void; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index a561fa1..dd00d37 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,20 +1,20 @@ import type { AppProps } from "next/app"; -import Navbar from "@/components/layout/Navbar"; -import Footer from "@/components/layout/Footer"; +import Navbar from "components/layout/Navbar"; +import Footer from "components/layout/Footer"; import NextNProgress from "nextjs-progressbar"; -import "@/styles/globals.css"; -import "@/styles/markdown.css"; -import "@/styles/highlight.css"; -import "@config/site.config"; +import "styles/globals.css"; +import "styles/markdown.css"; +import "styles/highlight.css"; +import "config/site.config"; import "react-toastify/dist/ReactToastify.min.css"; -import { Exo_2, Source_Sans_Pro, JetBrains_Mono } from "next/font/google"; +import { Exo_2, JetBrains_Mono, Source_Sans_Pro } from "next/font/google"; import { IconContext } from "react-icons"; import { ToastContainer } from "react-toastify"; -import useLocalStorage from "@hooks/useLocalStorage"; +import useLocalStorage from "hooks/useLocalStorage"; import { DefaultSeo, DefaultSeoProps } from "next-seo"; -import config from "@config/site.config"; +import config from "config/site.config"; const exo2 = Exo_2({ weight: ["300", "400", "600", "700"], diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 7e49d91..c10c3f7 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -1,4 +1,4 @@ -import { Html, Head, Main, NextScript } from "next/document"; +import { Head, Html, Main, NextScript } from "next/document"; export default function Document() { return ( diff --git a/src/pages/api/download/[id]/[fileName].ts b/src/pages/api/download/[id]/[fileName].ts new file mode 100644 index 0000000..db2f7d3 --- /dev/null +++ b/src/pages/api/download/[id]/[fileName].ts @@ -0,0 +1,67 @@ +import initMiddleware from "utils/apiMiddleware"; +import { NextApiRequest, NextApiResponse } from "next"; +import { ErrorResponse } from "types/googleapis"; +import driveClient from "utils/driveClient"; +import { ExtendedError } from "utils/driveHelper"; +import apiConfig from "config/api.config"; + +export default initMiddleware(async function handler( + request: NextApiRequest, + response: NextApiResponse, +) { + const _start = Date.now(); + try { + const { id, fileName } = request.query; + const getFileMetadata = await driveClient.files.get({ + fileId: id as string, + fields: "name, mimeType, size, webContentLink", + }); + + if ( + getFileMetadata.data.name !== decodeURIComponent(fileName as string) || + getFileMetadata.data.mimeType?.startsWith("application/vnd.google-apps") + ) { + throw new ExtendedError("File not found", 404, "notFound"); + } + + if (Number(getFileMetadata.data.size) > apiConfig.maxResponseSize) { + return response + .status(301) + .redirect(getFileMetadata.data.webContentLink as string); + } + + const getFileStream = await driveClient.files.get( + { + fileId: id as string, + alt: "media", + }, + { responseType: "stream" }, + ); + + response.setHeader( + "Content-Type", + getFileMetadata.data.mimeType || "application/octet-stream", + ); + response.setHeader( + "Content-Disposition", + `attachment; filename=${encodeURIComponent( + getFileMetadata.data.name as string, + )}`, + ); + + return response.status(200).send(getFileStream.data); + } catch (error: any) { + const payload: ErrorResponse = { + success: false, + timestamp: new Date().toISOString(), + responseTime: Date.now() - _start, + code: error.code || 500, + errors: { + message: error.errors?.[0].message || error.message || "Unknown error", + reason: error.errors?.[0].reason || error.cause || "internalError", + }, + }; + + return response.status(payload.code || 500).json(payload); + } +}); diff --git a/src/pages/api/encrypt.ts b/src/pages/api/encrypt.ts new file mode 100644 index 0000000..6558716 --- /dev/null +++ b/src/pages/api/encrypt.ts @@ -0,0 +1,65 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { ErrorResponse } from "types/googleapis"; +import { ExtendedError } from "utils/driveHelper"; +import { + decrypt, + encrypt, + urlDecrypt, + urlEncrypt, +} from "utils/encryptionHelper"; + +export default function handler( + request: NextApiRequest, + response: NextApiResponse, +) { + const _start = Date.now(); + try { + const { text, isUrl, isDecrypt } = request.query; + if (!text) throw new ExtendedError("Missing text", 400, "missingText"); + + let encrypted; + let decrypted; + if (!isDecrypt) { + if (isUrl) { + const encodedText = encodeURIComponent(text as string); + encrypted = urlEncrypt(encodedText); + } else { + const encodedText = encodeURIComponent(text as string); + encrypted = encrypt(encodedText); + } + } else { + if (isUrl) { + decrypted = decodeURIComponent(urlDecrypt(text as string)); + } else { + decrypted = decodeURIComponent(decrypt(text as string)); + } + } + + const payload = { + success: true, + timestamp: new Date().toISOString(), + responseTime: Date.now() - _start, + data: { + isDecrypt: !!isDecrypt, + isUrl: !!isUrl, + encrypted, + decrypted, + }, + }; + + return response.status(200).json(payload); + } catch (error: any) { + const payload: ErrorResponse = { + success: false, + timestamp: new Date().toISOString(), + responseTime: Date.now() - _start, + code: error.code || 500, + errors: { + message: error.errors?.[0].message || error.message || "Unknown error", + reason: error.errors?.[0].reason || error.cause || "internalError", + }, + }; + + return response.status(payload.code || 500).json(payload); + } +} diff --git a/src/pages/api/experimental/[...path].ts b/src/pages/api/experimental/[...path].ts deleted file mode 100644 index 36c9396..0000000 --- a/src/pages/api/experimental/[...path].ts +++ /dev/null @@ -1,146 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import drive from "@utils/driveClient"; -import apiConfig from "@/config/api.config"; -import { FileResponse, FilesResponse } from "@/types/googleapis"; -import { ExtendedError } from "@/types/default"; - -export default async function handler( - request: NextApiRequest, - response: NextApiResponse, -) { - const _start = Date.now(); - try { - const pathArray = request.query.path as string[]; - const [folderName, partialId] = pathArray[pathArray.length - 1].split("."); - - const searchFile = await drive.files.list({ - q: `name = '${folderName}' and 'me' in owners and trashed = false`, - fields: "files(id, name, mimeType), nextPageToken", - }); - - const findPath = searchFile.data.files?.filter((file) => { - return file.name === folderName && file.id?.slice(0, 8) === partialId; - })[0]; - - if (!findPath) { - const error = new Error("File not found") as ExtendedError; - error.cause = "notFound"; - error.code = 404; - throw error; - } - - let isReadmeFile = false; - let payload: FileResponse | FilesResponse; - let description = ""; - if (findPath.mimeType === "application/vnd.google-apps.folder") { - const fetchFolder = await drive.files.list({ - q: `'${findPath.id}' in parents and trashed = false and 'me' in owners`, - fields: - "files(id, name, mimeType, parents, description), nextPageToken", - }); - isReadmeFile = !!fetchFolder.data.files?.some( - (file) => file.name === ".readme.md", - ); - - payload = { - success: true, - timestamp: new Date().toISOString(), - durationMs: 0, - readmeExists: isReadmeFile, - nextPageToken: fetchFolder.data.nextPageToken || undefined, - files: - fetchFolder.data.files?.filter( - (file) => file.mimeType !== "application/vnd.google-apps.folder", - ) || [], - folders: - fetchFolder.data.files?.filter( - (file) => file.mimeType === "application/vnd.google-apps.folder", - ) || [], - } as FilesResponse; - } else { - const fetchFile = await drive.files.get({ - fileId: findPath.id as string, - fields: - "id, name, mimeType, parents, thumbnailLink, fileExtension, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, exportLinks", - }); - payload = { - success: true, - timestamp: new Date().toISOString(), - durationMs: 0, - file: fetchFile.data, - } as FileResponse; - } - - // loop through pathArray and check if any of the paths are in protectedFOlders - // if so, set isProtected to true - let isProtected = false; - let isValidated = false; - let protectedId = ""; - for (let i = 0; i < pathArray.length; i++) { - const partialId = pathArray[i].split(".")[1]; - for (let j = 0; j < apiConfig.protectedFolders.length; j++) { - if (apiConfig.protectedFolders[j].slice(0, 8) === partialId) { - isProtected = true; - protectedId = apiConfig.protectedFolders[j]; - break; - } - } - } - - if (protectedId) { - const findPassword = await drive.files.get({ - fileId: protectedId, - fields: "description", - }); - if (findPassword.data.description) { - description = findPassword.data.description; - const { password: passwordQuery } = request.query; - if (findPassword.data.description === passwordQuery) { - isValidated = true; - } - } - } - - // if (isProtected) { - // const findPassword = await drive.files.list({ - // q: `name = '.password' and '${protectedId}' in parents and trashed = false and 'me' in owners`, - // fields: "files(id)", - // }); - // if (findPassword.data.files?.length) { - // const passwordFile = await drive.files.get({ - // fileId: findPassword.data.files[0].id as string, - // alt: "media", - // }); - // const password = passwordFile.data as unknown as string; - // const { password: passwordQuery } = request.query; - // if (password === passwordQuery) { - // isValidated = true; - // } - // } - // } - // if (isProtected && !isValidated) { - // const error = new Error("Protected folder") as ExtendedError; - // error.cause = "protected"; - // error.code = 403; - // throw error; - // } - - payload.passwordRequired = isProtected; - payload.passwordValidated = isValidated; - payload.protectedId = protectedId; - - const _end = Date.now(); - payload.durationMs = _end - _start; - - return response.status(200).json({ payload, description }); - } catch (error: any) { - console.error(error); - const _end = Date.now(); - return response.status(500).json({ - success: false, - timestamp: new Date().toISOString(), - durationMs: _end - _start, - error: error.message, - }); - } -} diff --git a/src/pages/api/experimental/files/index.ts b/src/pages/api/experimental/files/index.ts deleted file mode 100644 index e20fc62..0000000 --- a/src/pages/api/experimental/files/index.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next"; -import drive, { redis } from "@utils/driveClient"; -import config from "@/config/site.config"; -import { ErrorResponse, FilesResponse } from "@/types/googleapis"; -import { urlEncrypt } from "@/utils/encryptionHelper"; -import { ExtendedError } from "@/types/default"; - -export default async function handler( - request: NextApiRequest, - response: NextApiResponse, -) { - const _start = Date.now(); - - try { - const cacheKey = request.url as string; - const { isRefresh } = request.query; - - if (!isRefresh) { - const cachedResponse = await redis.get(cacheKey); - if (cachedResponse) { - const _end = Date.now(); - return response - .status(200) - .setHeader("Cache-Control", "max-age=60") - .json({ ...cachedResponse, durationMs: _end - _start }); - } - } - - const { pageToken } = request.query; - const authorization = - process.env.NODE_ENV === "development" - ? request.query.hash - : request.headers.authorization?.split(" ")[1] || null; - - const query = [ - `'${config.files.rootFolder}' in parents`, - "trashed = false", - "'me' in owners", - ]; - const promiseFolderContents = await drive.files.list({ - q: `${query.join(" and ")}`, - fields: - "files(id, name, mimeType, thumbnailLink, fileExtension, createdTime, modifiedTime, size, videoMediaMetadata), nextPageToken", - orderBy: "folder, name asc", - pageSize: config.files.itemsPerPage, - pageToken: (pageToken as string) || undefined, - }); - - const passwordFile = promiseFolderContents.data.files?.find( - (file) => file.name === ".password", - ); - const readmeFile = promiseFolderContents.data.files?.find( - (file) => file.name === ".readme.md", - ); - const folderList = - promiseFolderContents.data.files?.filter( - (item) => item.mimeType === "application/vnd.google-apps.folder", - ) || []; - const fileList = - promiseFolderContents.data.files?.filter( - (item) => item.mimeType !== "application/vnd.google-apps.folder", - ) || []; - - // if (passwordFile && !authorization) { - // const error: ExtendedError = new Error("Unauthorized"); - // error.code = 401; - // error.cause = "unauthorized"; - // throw error; - // } - - if (readmeFile && authorization) { - const validatePassword = await drive.files.get( - { - fileId: promiseFolderContents.data.files?.find( - (file) => file.name === ".password", - )?.id as string, - alt: "media", - }, - { responseType: "text" }, - ); - if (validatePassword.data !== authorization) { - const error: ExtendedError = new Error("Unauthorized"); - error.code = 401; - error.cause = "unauthorized"; - throw error; - } - } - - const _end = Date.now(); - const payload: FilesResponse = { - success: true, - timestamp: new Date().toISOString(), - durationMs: _end - _start, - passwordRequired: passwordFile ? true : false, - passwordValidated: true, - protectedId: "", - parents: [], - files: fileList.map((file) => ({ - ...file, - id: urlEncrypt(file.id as string), - })), - folders: folderList, - readmeExists: readmeFile ? true : false, - nextPageToken: promiseFolderContents.data.nextPageToken || undefined, - }; - - await redis.set(cacheKey as string, JSON.stringify(payload), { - ex: 60, - }); - return response - .status(200) - .setHeader("Cache-Control", "s-maxage=60, stale-while-revalidate") - .json(payload); - } catch (error: any) { - const _end = Date.now(); - console.log(error); - const payload: ErrorResponse = { - success: false, - timestamp: new Date().toISOString(), - durationMs: _end - _start, - code: error.code || 500, - errors: { - message: error.errors?.[0].message || error.message || "Unknown error", - reason: error.errors?.[0].reason || error.cause || "internalError", - }, - }; - - return response.status(payload.code || 500).json(payload); - } -} diff --git a/src/pages/api/experimental/pathEncryptionHelper.ts b/src/pages/api/experimental/pathEncryptionHelper.ts deleted file mode 100644 index 5449230..0000000 --- a/src/pages/api/experimental/pathEncryptionHelper.ts +++ /dev/null @@ -1,17 +0,0 @@ -import crypto from "node:crypto"; - -const iv = crypto.randomBytes(16); -const key = "thisisa16bytekey"; -export function encryptString(plainText: string): string { - const cipher = crypto.createCipheriv("aes-128-cbc", key, iv); - let cipherText = cipher.update(plainText, "utf8", "hex"); - cipherText += cipher.final("hex"); - return cipherText; -} - -export function decryptString(chiperText: string): string { - const decipher = crypto.createDecipheriv("aes-128-cbc", key, iv); - let plainText = decipher.update(chiperText, "hex", "utf8"); - plainText += decipher.final("utf8"); - return plainText; -} diff --git a/src/pages/api/files/[id]/index.ts b/src/pages/api/files/[id]/index.ts new file mode 100644 index 0000000..cf357ea --- /dev/null +++ b/src/pages/api/files/[id]/index.ts @@ -0,0 +1,133 @@ +//Accepted id is Name#PartialID + +import { NextApiRequest, NextApiResponse } from "next"; +import { ErrorResponse, FileResponse, FilesResponse } from "types/googleapis"; +import { ExtendedError, hiddenFiles } from "utils/driveHelper"; +import driveClient from "utils/driveClient"; +import apiConfig from "config/api.config"; +import initMiddleware from "utils/apiMiddleware"; +import { urlEncrypt } from "utils/encryptionHelper"; + +export default initMiddleware(async function handler( + request: NextApiRequest, + response: NextApiResponse, +) { + const _start = Date.now(); + + try { + const { id, pageToken } = request.query; + + const [name, partialId] = (id as string).split(":"); + + if (!name || !partialId || partialId.length !== 8) { + throw new ExtendedError( + "Can't resolve name and id provided.", + 400, + "invalidId", + ); + } + + const searchForFile = await driveClient.files.list({ + q: `name contains '${name}' and trashed = false and 'me' in owners`, + fields: + "files(id, name, mimeType, thumbnailLink, fileExtension, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, webContentLink)", + }); + + const file = searchForFile.data.files?.find( + (file) => + file.name === decodeURIComponent(name) && + (file.id as string).startsWith(partialId), + ); + if (!file) { + throw new ExtendedError("File not found.", 404, "notFound"); + } + + response.setHeader("Cache-Control", apiConfig.cache); + + if (file.mimeType !== "application/vnd.google-apps.folder") { + const payload: FileResponse = { + success: true, + timestamp: new Date().toISOString(), + responseTime: Date.now() - _start, + file: { + ...file, + id: urlEncrypt(file.id as string), + webContentLink: file.webContentLink + ? urlEncrypt(file.webContentLink) + : undefined, + }, + }; + + return response.status(200).json(payload); + } + + const query = [ + `'${file.id}' in parents`, + "trashed = false", + "'me' in owners", + ]; + const fetchFolderContents = await driveClient.files.list({ + q: `${query.join(" and ")}`, + fields: + "files(id, name, mimeType, thumbnailLink, fileExtension, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, webContentLink), nextPageToken", + orderBy: "folder, name asc, createdTime", + pageSize: apiConfig.files.itemsPerPage, + pageToken: (pageToken as string) || undefined, + }); + + const isReadmeExists = !!fetchFolderContents.data.files?.find( + (file) => file.name === ".readme.md", + ); + // Get only folder, since we order the folder to be first, we can just get all the folder first + const folderList = + fetchFolderContents.data.files + ?.filter( + (item) => item.mimeType === "application/vnd.google-apps.folder", + ) + .map((item) => ({ + ...item, + id: urlEncrypt(item.id as string), + })) || []; + // Filter the files, excluding every google apps files and hidden files (.password and .readme.md) + // .password currently not used, but will probably be used in the future + const fileList = + fetchFolderContents.data.files + ?.filter( + (item) => + !item.mimeType?.startsWith("application/vnd.google-apps") && + !hiddenFiles.includes(item.name as string), + ) + .map((item) => ({ + ...item, + id: urlEncrypt(item.id as string), + webContentLink: item.webContentLink + ? urlEncrypt(item.webContentLink) + : undefined, + })) || []; + + const payload: FilesResponse = { + success: true, + timestamp: new Date().toISOString(), + responseTime: Date.now() - _start, + isReadmeExists: isReadmeExists, + folders: folderList, + files: fileList, + nextPageToken: fetchFolderContents.data.nextPageToken || undefined, + }; + + return response.status(200).json(payload); + } catch (error: any) { + const payload: ErrorResponse = { + success: false, + timestamp: new Date().toISOString(), + responseTime: Date.now() - _start, + code: error.code || 500, + errors: { + message: error.errors?.[0].message || error.message || "Unknown error", + reason: error.errors?.[0].reason || error.cause || "internalError", + }, + }; + + return response.status(payload.code || 500).json(payload); + } +}); diff --git a/src/pages/api/files/index.ts b/src/pages/api/files/index.ts index 913d5a9..d97eeeb 100644 --- a/src/pages/api/files/index.ts +++ b/src/pages/api/files/index.ts @@ -1,78 +1,81 @@ import { NextApiRequest, NextApiResponse } from "next"; -import drive from "@utils/driveClient"; -import config from "@/config/site.config"; -import { ErrorResponse, FilesResponse } from "@/types/googleapis"; -import { urlEncrypt } from "@/utils/encryptionHelper"; -import { ExtendedError } from "@/types/default"; -import apiConfig from "@/config/api.config"; -import { hiddenFiles } from "@/utils/driveHelper"; +import driveClient from "utils/driveClient"; +import apiConfig from "config/api.config"; +import { hiddenFiles } from "utils/driveHelper"; +import initMiddleware from "utils/apiMiddleware"; +import { ErrorResponse, FilesResponse } from "types/googleapis"; +import { urlEncrypt } from "utils/encryptionHelper"; -export default async function handler( +export default initMiddleware(async function handler( request: NextApiRequest, - response: NextApiResponse, + response: NextApiResponse, ) { const _start = Date.now(); try { const { pageToken } = request.query; - const query = [ - `'${config.files.rootFolder}' in parents`, - "trashed = false", - "'me' in owners", - ]; - const fetchFolderContents = await drive.files.list({ + const query = ["trashed = false", "'me' in owners"]; + if (apiConfig.files.rootFolder === "root") { + query.push("parents = 'root'"); + } else { + query.push(`parents = '${apiConfig.files.rootFolder}'`); + } + const fetchFolderContents = await driveClient.files.list({ q: `${query.join(" and ")}`, fields: - "files(id, name, mimeType, thumbnailLink, fileExtension, createdTime, modifiedTime, size, videoMediaMetadata), nextPageToken", + "files(id, name, mimeType, thumbnailLink, fileExtension, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, webContentLink), nextPageToken", orderBy: "folder, name asc, createdTime", pageSize: apiConfig.files.itemsPerPage, pageToken: (pageToken as string) || undefined, }); - const readmeFile = fetchFolderContents.data.files?.find( + const isReadmeExists = !!fetchFolderContents.data.files?.find( (file) => file.name === ".readme.md", ); const folderList = - fetchFolderContents.data.files?.filter( - (item) => item.mimeType === "application/vnd.google-apps.folder", - ) || []; + fetchFolderContents.data.files + ?.filter( + (item) => item.mimeType === "application/vnd.google-apps.folder", + ) + .map((item) => ({ + ...item, + id: urlEncrypt(item.id as string), + })) || []; const fileList = - fetchFolderContents.data.files?.filter( - (item) => - item.mimeType !== "application/vnd.google-apps.folder" && - !hiddenFiles.includes(item.name as string), - ) || []; - let readmeContents: string = ""; - if (readmeFile) { - const fetchReadmeContents = await drive.files.get({ - fileId: readmeFile.id as string, - alt: "media", - }); - readmeContents = fetchReadmeContents.data as string; - } + fetchFolderContents.data.files + ?.filter( + (item) => + !item.mimeType?.startsWith("application/vnd.google-apps") && + !hiddenFiles.includes(item.name as string), + ) + .map((item) => ({ + ...item, + id: urlEncrypt(item.id as string), + webContentLink: item.webContentLink + ? urlEncrypt(item.webContentLink) + : undefined, + })) || []; - const _end = Date.now(); const payload: FilesResponse = { success: true, timestamp: new Date().toISOString(), - responseTime: _end - _start, + responseTime: Date.now() - _start, folders: folderList, files: fileList, - readmeContents, - nextPageToken = fetchFolderContents.data.nextPageToken || undefined + isReadmeExists: isReadmeExists, + nextPageToken: fetchFolderContents.data.nextPageToken || undefined, }; return response .status(200) - .setHeader("Cache-Control", "s-maxage=60, stale-while-revalidate") + .setHeader("Cache-Control", apiConfig.cache) .json(payload); } catch (error: any) { - const _end = Date.now(); const payload: ErrorResponse = { success: false, timestamp: new Date().toISOString(), - responseTime: _end - _start, + responseTime: Date.now() - _start, code: error.code || 500, errors: { message: error.errors?.[0].message || error.message || "Unknown error", @@ -82,4 +85,4 @@ export default async function handler( return response.status(payload.code || 500).json(payload); } -} +}); diff --git a/src/pages/api/legacy/files/[id]/index.ts b/src/pages/api/legacy/files/[id]/index.ts index 7b1719d..45ae644 100644 --- a/src/pages/api/legacy/files/[id]/index.ts +++ b/src/pages/api/legacy/files/[id]/index.ts @@ -113,7 +113,6 @@ export default async function handler( (file) => file.mimeType !== "application/vnd.google-apps.folder", ) || []; - const _end = Date.now(); const payload: FilesResponse = { success: true, timestamp: new Date().toISOString(), @@ -130,7 +129,6 @@ export default async function handler( return response.status(200).json(payload); } - const _end = Date.now(); const payload: FileResponse = { success: true, timestamp: new Date().toISOString(), diff --git a/src/pages/api/legacy/search.ts b/src/pages/api/legacy/search.ts new file mode 100644 index 0000000..b04a64c --- /dev/null +++ b/src/pages/api/legacy/search.ts @@ -0,0 +1,68 @@ +import { ErrorResponse, SearchResponse } from "@/types/googleapis"; +import drive from "@/utils/driveClient"; +import { buildQuery } from "@/utils/driveHelper"; +import { NextApiRequest, NextApiResponse } from "next"; +import config from "@config/site.config"; + +export default async function handler( + request: NextApiRequest, + response: NextApiResponse, +) { + try { + const { query } = request.query; + if (!query) { + const payload: SearchResponse = { + success: true, + timestamp: new Date().toISOString(), + files: [], + }; + + return response.status(200).json(payload); + } + + const fetchFiles = await drive.files.list({ + q: buildQuery({ + extraQuery: [`name contains '${query}'`], + globalSearch: true, + }), + fields: + "files(id, name, mimeType, thumbnailLink, fileExtension, createdTime, modifiedTime, size)", + pageSize: config.files.searchResult, + }); + + const payload: SearchResponse = { + success: true, + timestamp: new Date().toISOString(), + files: fetchFiles.data.files || [], + }; + + return response.status(200).json(payload); + } catch (error: any) { + if (error satisfies ErrorResponse) { + const payload: ErrorResponse = { + success: false, + timestamp: new Date().toISOString(), + code: error.code || 500, + errors: { + message: + error.errors?.[0].message || error.message || "Unknown error", + reason: error.errors?.[0].reason || error.cause || "internalError", + }, + }; + + return response.status(error.code).json(payload); + } + + const payload: ErrorResponse = { + success: false, + timestamp: new Date().toISOString(), + code: error.code || 500, + errors: { + message: error.message || "Unknown error", + reason: error.cause || "internalError", + }, + }; + + return response.status(500).json(payload); + } +} diff --git a/src/pages/api/readme/index.ts b/src/pages/api/readme/index.ts index 73966d0..45600e5 100644 --- a/src/pages/api/readme/index.ts +++ b/src/pages/api/readme/index.ts @@ -1,6 +1,5 @@ -import { ErrorResponse } from "@/types/googleapis"; -import { buildQuery } from "@/utils/driveHelper"; -import drive from "@/utils/driveClient"; +import { ErrorResponse } from "types/googleapis"; +import drive from "utils/driveClient"; import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( @@ -9,7 +8,7 @@ export default async function handler( ) { try { const fetchReadme = await drive.files.list({ - q: buildQuery({ extraQuery: ["name = 'readme.md'"] }), + q: "", fields: "files(id, name, mimeType, size)", orderBy: "modifiedTime desc", }); diff --git a/src/pages/api/search.ts b/src/pages/api/search.ts index b04a64c..cd20cb7 100644 --- a/src/pages/api/search.ts +++ b/src/pages/api/search.ts @@ -1,68 +1,70 @@ -import { ErrorResponse, SearchResponse } from "@/types/googleapis"; -import drive from "@/utils/driveClient"; -import { buildQuery } from "@/utils/driveHelper"; +import apiConfig from "config/api.config"; import { NextApiRequest, NextApiResponse } from "next"; -import config from "@config/site.config"; +import { ErrorResponse, SearchResponse } from "types/googleapis"; +import driveClient from "utils/driveClient"; +import { urlEncrypt } from "utils/encryptionHelper"; +import { hiddenFiles } from "utils/driveHelper"; export default async function handler( request: NextApiRequest, response: NextApiResponse, ) { + const _start = Date.now(); + try { - const { query } = request.query; - if (!query) { - const payload: SearchResponse = { + const { q } = request.query; + + if (!q) { + return response.status(200).json({ success: true, timestamp: new Date().toISOString(), + responseTime: Date.now() - _start, files: [], - }; - - return response.status(200).json(payload); + }); } - const fetchFiles = await drive.files.list({ - q: buildQuery({ - extraQuery: [`name contains '${query}'`], - globalSearch: true, - }), + const searchFile = await driveClient.files.list({ + q: `name contains '${q}' and 'me' in owners and trashed = false`, fields: "files(id, name, mimeType, thumbnailLink, fileExtension, createdTime, modifiedTime, size)", - pageSize: config.files.searchResult, + pageSize: apiConfig.files.itemsPerPage, + orderBy: "modifiedTime desc", }); const payload: SearchResponse = { success: true, timestamp: new Date().toISOString(), - files: fetchFiles.data.files || [], + responseTime: Date.now() - _start, + files: + searchFile.data.files + ?.filter( + (item) => + !hiddenFiles.includes(item.name as string) && + (!item.mimeType?.startsWith("application/vnd.google-apps") || + item.mimeType === "application/vnd.google-apps.folder"), + ) + .map((item) => ({ + ...item, + id: urlEncrypt(item.id as string), + })) || [], }; - return response.status(200).json(payload); + return response + .status(200) + .setHeader("Cache-Control", apiConfig.cache) + .json(payload); } catch (error: any) { - if (error satisfies ErrorResponse) { - const payload: ErrorResponse = { - success: false, - timestamp: new Date().toISOString(), - code: error.code || 500, - errors: { - message: - error.errors?.[0].message || error.message || "Unknown error", - reason: error.errors?.[0].reason || error.cause || "internalError", - }, - }; - - return response.status(error.code).json(payload); - } - const payload: ErrorResponse = { success: false, timestamp: new Date().toISOString(), + responseTime: Date.now() - _start, code: error.code || 500, errors: { - message: error.message || "Unknown error", - reason: error.cause || "internalError", + message: error.errors?.[0].message || error.message || "Unknown error", + reason: error.errors?.[0].reason || error.cause || "internalError", }, }; - return response.status(500).json(payload); + return response.status(payload.code || 500).json(payload); } } diff --git a/src/pages/download/[id]/[fileName].tsx b/src/pages/download/[id]/[fileName].tsx deleted file mode 100644 index 3f73692..0000000 --- a/src/pages/download/[id]/[fileName].tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { promisify } from "util"; -import { pipeline } from "stream"; -import { GetServerSideProps } from "next"; -import drive from "@utils/driveClient"; - -export default function Download() { - return
; -} - -const pipelineAsync = promisify(pipeline); - -export const getServerSideProps: GetServerSideProps = async ({ - res, - query, -}) => { - const { id, fileName } = query; - const getImageMetadata = drive.files.get({ - fileId: id as string, - fields: "name, mimeType", - }); - const getImageStream = drive.files.get( - { - fileId: id as string, - alt: "media", - }, - { responseType: "stream" }, - ); - - const [{ data: imageMetadata }, imageStream] = await Promise.all([ - getImageMetadata, - getImageStream, - ]); - // Check fileName === image metadata - if (fileName !== imageMetadata.name) { - return { - notFound: true, - }; - } - res.setHeader( - "Content-Disposition", - `attachment; filename=${encodeURIComponent(imageMetadata.name as string)}`, - ); - res.setHeader( - "Content-Type", - (imageMetadata.mimeType as string) || "application/octet-stream", - ); - await pipelineAsync(imageStream.data, res); - return { - props: {}, - }; -}; diff --git a/src/pages/file/[id].tsx b/src/pages/file/[id].tsx index 90f39ad..f4c5698 100644 --- a/src/pages/file/[id].tsx +++ b/src/pages/file/[id].tsx @@ -1,15 +1,15 @@ import useSWR from "swr"; -import { ErrorResponse, FileResponse, TFileParent } from "@/types/googleapis"; -import Breadcrumb from "@/components/Breadcrumb"; +import { ErrorResponse, FileResponse, TFileParent } from "types/googleapis"; +import Breadcrumb from "components/Breadcrumb"; import { useCallback, useEffect, useState } from "react"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; import { useRouter } from "next/router"; -import FileDetails from "@components/layout/FileDetails"; -import useLocalStorage from "@hooks/useLocalStorage"; +import FileDetails from "components/layout/FileDetails"; +import useLocalStorage from "hooks/useLocalStorage"; import axios from "axios"; import { GetServerSidePropsContext } from "next"; -import Password from "@components/layout/Password"; +import Password from "components/layout/Password"; import { NextSeo } from "next-seo"; type Props = { diff --git a/src/pages/folder/[id].tsx b/src/pages/folder/[id].tsx index 3e9e9f7..eeeecd4 100644 --- a/src/pages/folder/[id].tsx +++ b/src/pages/folder/[id].tsx @@ -1,21 +1,21 @@ import useSWR from "swr"; import useSWRInfinite from "swr/infinite"; -import fetcher, { buildNextKey } from "@utils/swrFetch"; -import { ErrorResponse, FilesResponse, TFile } from "@/types/googleapis"; -import Breadcrumb from "@/components/Breadcrumb"; +import fetcher, { buildNextKey } from "utils/swrFetch"; +import { ErrorResponse, FilesResponse, TFile } from "types/googleapis"; +import Breadcrumb from "components/Breadcrumb"; import { drive_v3 } from "googleapis"; import { useCallback, useEffect, useState } from "react"; -import MarkdownRender from "@/components/utility/MarkdownRender"; -import config from "@config/site.config"; -import GridLayout from "@components/layout/Files/GridLayout"; -import useLocalStorage from "@hooks/useLocalStorage"; -import SwitchLayout from "@components/utility/SwitchLayout"; -import ListLayout from "@components/layout/Files/ListLayout"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; +import MarkdownRender from "components/utility/MarkdownRender"; +import config from "config/site.config"; +import GridLayout from "components/layout/Files/GridLayout"; +import useLocalStorage from "hooks/useLocalStorage"; +import SwitchLayout from "components/utility/SwitchLayout"; +import ListLayout from "components/layout/Files/ListLayout"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; import { useRouter } from "next/router"; import axios from "axios"; -import Password from "@components/layout/Password"; +import Password from "components/layout/Password"; import { GetServerSidePropsContext } from "next"; import { NextSeo } from "next-seo"; @@ -109,11 +109,11 @@ export default function Folder({ passwordParent, folderName }: Props) { swrData?.flatMap((item: FilesResponse) => item.folders); const newData: FilesResponse = { ...(swrData?.[size - 1] as FilesResponse), - files: files || [], - folders: folders || [], + files: (files as drive_v3.Schema$File[]) || [], + folders: (folders as drive_v3.Schema$File[]) || [], }; setData(newData); - if (newData.readmeExists) setIsReadmeExists(true); + if (newData.isReadmeExists) setIsReadmeExists(true); setGlobalLoading(false); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/pages/index.tsx b/src/pages/index.tsx index db4cb3b..674e68c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,20 +1,20 @@ import useSWR from "swr"; import useSWRInfinite from "swr/infinite"; -import fetcher, { buildNextKey } from "@utils/swrFetch"; -import { ErrorResponse, FilesResponse, TFile } from "@/types/googleapis"; -import Breadcrumb from "@/components/Breadcrumb"; +import fetcher, { buildNextKey } from "utils/swrFetch"; +import { ErrorResponse, FilesResponse, TFile } from "types/googleapis"; +import Breadcrumb from "components/Breadcrumb"; import { drive_v3 } from "googleapis"; import { useCallback, useEffect, useState } from "react"; -import MarkdownRender from "@/components/utility/MarkdownRender"; -import config from "@config/site.config"; -import GridLayout from "@components/layout/Files/GridLayout"; -import useLocalStorage from "@hooks/useLocalStorage"; -import SwitchLayout from "@components/utility/SwitchLayout"; -import ListLayout from "@components/layout/Files/ListLayout"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import ErrorFeedback from "@components/APIFeedback/Error"; +import MarkdownRender from "components/utility/MarkdownRender"; +import config from "config/site.config"; +import GridLayout from "components/layout/Files/GridLayout"; +import useLocalStorage from "hooks/useLocalStorage"; +import SwitchLayout from "components/utility/SwitchLayout"; +import ListLayout from "components/layout/Files/ListLayout"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import ErrorFeedback from "components/APIFeedback/Error"; import axios from "axios"; -import Password from "@components/layout/Password"; +import Password from "components/layout/Password"; export default function Home() { const [data, setData] = useState(); @@ -94,11 +94,11 @@ export default function Home() { swrData?.flatMap((item: FilesResponse) => item.folders); const newData: FilesResponse = { ...(swrData?.[size - 1] as FilesResponse), - files: files || [], - folders: folders || [], + files: (files as drive_v3.Schema$File[]) || [], + folders: (folders as drive_v3.Schema$File[]) || [], }; setData(newData); - if (newData.readmeExists) setIsReadmeExists(true); + if (newData.isReadmeExists) setIsReadmeExists(true); setGlobalLoading(false); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/pages/media/[id]/[fileName].tsx b/src/pages/media/[id]/[fileName].tsx index 50506ee..5b71782 100644 --- a/src/pages/media/[id]/[fileName].tsx +++ b/src/pages/media/[id]/[fileName].tsx @@ -1,7 +1,7 @@ import { promisify } from "util"; import { pipeline } from "stream"; import { GetServerSideProps } from "next"; -import drive from "@utils/driveClient"; +import drive from "utils/driveClient"; export default function Media() { return
; @@ -34,8 +34,7 @@ export const getServerSideProps: GetServerSideProps = async ({ if ( !(imageMetadata.mimeType as string).startsWith("image/") && !(imageMetadata.mimeType as string).startsWith("video/") && - !(imageMetadata.mimeType as string).startsWith("audio/") && - !(imageMetadata.mimeType as string).startsWith("application/pdf") + !(imageMetadata.mimeType as string).startsWith("audio/") ) { return { notFound: true, diff --git a/src/pages/setup/encryption.tsx b/src/pages/setup/encryption.tsx index 5e22404..4c6552c 100644 --- a/src/pages/setup/encryption.tsx +++ b/src/pages/setup/encryption.tsx @@ -3,11 +3,11 @@ import { useEffect, useState } from "react"; import { createEncryptionKey, generateRandomEncryptionKey, -} from "@utils/encryptionHelper"; -import useCopyText from "@hooks/useCopyText"; +} from "utils/encryptionHelper"; +import useCopyText from "hooks/useCopyText"; import { toast } from "react-toastify"; import Link from "next/link"; -import useLocalStorage from "@hooks/useLocalStorage"; +import useLocalStorage from "hooks/useLocalStorage"; export default function Encryption() { const [settingJson, setSettingJson] = useLocalStorage("tempEncryption", ""); diff --git a/src/pages/setup/final.tsx b/src/pages/setup/final.tsx index fa8124f..1fb0564 100644 --- a/src/pages/setup/final.tsx +++ b/src/pages/setup/final.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; -import LoadingFeedback from "@components/APIFeedback/Loading"; -import MarkdownRender from "@components/utility/MarkdownRender"; -import useLocalStorage from "@hooks/useLocalStorage"; +import LoadingFeedback from "components/APIFeedback/Loading"; +import MarkdownRender from "components/utility/MarkdownRender"; +import useLocalStorage from "hooks/useLocalStorage"; import { useEffect, useState } from "react"; type ConfigProps = { diff --git a/src/pages/setup/google-cloud.tsx b/src/pages/setup/google-cloud.tsx index b5a4e7c..9ef3235 100644 --- a/src/pages/setup/google-cloud.tsx +++ b/src/pages/setup/google-cloud.tsx @@ -1,11 +1,11 @@ import { useEffect, useState } from "react"; -import { encrypt } from "@utils/encryptionHelper"; +import { encrypt } from "utils/encryptionHelper"; import Link from "next/link"; -import useLocalStorage from "@hooks/useLocalStorage"; -import MarkdownRender from "@components/utility/MarkdownRender"; +import useLocalStorage from "hooks/useLocalStorage"; +import MarkdownRender from "components/utility/MarkdownRender"; import useSWR from "swr"; -import fetcher from "@utils/swrFetch"; -import LoadingFeedback from "@components/APIFeedback/Loading"; +import fetcher from "utils/swrFetch"; +import LoadingFeedback from "components/APIFeedback/Loading"; export default function SetupGoogleCloud() { const [encryptionKey] = useLocalStorage("tempEncryption", ""); diff --git a/src/pages/test.tsx b/src/pages/test.tsx index 8f92703..297ae4c 100644 --- a/src/pages/test.tsx +++ b/src/pages/test.tsx @@ -1,5 +1,5 @@ import { GetServerSideProps } from "next"; -import driveClient from "@utils/driveClient"; +import driveClient from "utils/driveClient"; import { promisify } from "util"; import { pipeline } from "stream"; diff --git a/src/types/googleapis.ts b/src/types/googleapis.ts index def87d5..5a628f3 100644 --- a/src/types/googleapis.ts +++ b/src/types/googleapis.ts @@ -79,7 +79,7 @@ export interface FilesResponse extends APIResponse { parents?: TFileParent[]; folders: drive_v3.Schema$File[]; files: drive_v3.Schema$File[]; - readmeContents?: string; + isReadmeExists?: boolean; nextPageToken?: string; } diff --git a/src/utils/apiMiddleware.ts b/src/utils/apiMiddleware.ts index 610eb30..0fefa01 100644 --- a/src/utils/apiMiddleware.ts +++ b/src/utils/apiMiddleware.ts @@ -1,8 +1,8 @@ import { NextApiHandler, NextApiRequest, NextApiResponse } from "next"; import Cors, { CorsRequest } from "cors"; -import { ErrorResponse } from "@/types/googleapis"; +import { ErrorResponse } from "types/googleapis"; -function _initMiddleware(middleware: CorsRequest | any) { +export function _initMiddleware(middleware: CorsRequest | any) { return (req: NextApiRequest, res: NextApiResponse) => new Promise((resolve, reject) => { middleware(req, res, (result: any) => { diff --git a/src/utils/driveClient.ts b/src/utils/driveClient.ts index 70857fa..bc49f00 100644 --- a/src/utils/driveClient.ts +++ b/src/utils/driveClient.ts @@ -1,6 +1,6 @@ -import apiConfig from "@config/api.config"; -import { google, drive_v3 } from "googleapis"; -import { decrypt } from "@utils/encryptionHelper"; +import apiConfig from "config/api.config"; +import { drive_v3, google } from "googleapis"; +import { decrypt } from "utils/encryptionHelper"; import { Redis } from "@upstash/redis"; const decryptedSecret: string = decrypt( @@ -21,7 +21,9 @@ const oauth2Client = new google.auth.OAuth2( apiConfig.client_id, decryptedSecret, ); -oauth2Client.setCredentials({ refresh_token: decryptedRefreshToken }); +oauth2Client.setCredentials({ + refresh_token: decodeURIComponent(decryptedRefreshToken), +}); let gdriveInstance; if (!gdriveInstance) { diff --git a/src/utils/driveHelper.ts b/src/utils/driveHelper.ts index 0383d1a..d41aa59 100644 --- a/src/utils/driveHelper.ts +++ b/src/utils/driveHelper.ts @@ -1,88 +1,11 @@ -import drive from "@utils/driveClient"; -import config from "@config/site.config"; -import { verifyHash } from "@utils/hashHelper"; -import { TFileParent } from "@/types/googleapis"; +export const hiddenFiles = [".password", ".readme.md"]; -export function buildQuery({ - id, - extraQuery, - globalSearch = false, -}: { - id?: string; - extraQuery?: string[]; - globalSearch?: boolean; -}) { - const query = [ - "name != '.password'", - "'me' in owners", - "trashed = false", - // "mimeType != 'application/vnd.google-apps.shortcut'", - ]; +export class ExtendedError extends Error { + code?: number; - if (id && !globalSearch) { - query.unshift(`'${id}' in parents`); + constructor(message?: string, code?: number, reason?: string) { + super(message); + this.code = code; + this.cause = reason; } - if (!id && !globalSearch) { - query.unshift(`'${config.files.rootFolder}' in parents`); - } - - if (extraQuery) { - query.unshift(...extraQuery); - } - - return query.join(" and "); -} - -export const hiddenFiles = [ - '.password', - '.readme.md' -] - -export async function validateProtected( - fileId: string | TFileParent[], - passwordHash?: string, -): Promise<{ isProtected: boolean; valid?: boolean; protectedId?: string }> { - const fetchPassword = await drive.files.list({ - q: `name = '.password' and 'me' in owners and trashed = false`, - fields: "files(id, name, parents)", - pageSize: 1000, - }); - let passwordFile; - let protectedId; - if (typeof fileId === "string") { - passwordFile = fetchPassword.data.files?.find( - (file) => file.parents?.[0] === fileId, - ); - protectedId = fileId; - } - if (Array.isArray(fileId)) { - const parentsIdMap = fileId.map((parent) => parent.id); - passwordFile = fetchPassword.data.files?.find((file) => - parentsIdMap.includes(file.parents?.[0] as string), - ); - protectedId = passwordFile?.parents?.[0]; - } - - if (!passwordFile) return { isProtected: false }; - - const getPassword = await drive.files.get( - { - fileId: passwordFile.id as string, - alt: "media", - }, - { responseType: "text" }, - ); - - if (!passwordHash) - return { - isProtected: true, - valid: false, - protectedId, - }; - - return { - isProtected: true, - valid: verifyHash(getPassword.data as string, passwordHash), - protectedId, - }; } diff --git a/src/utils/encryptionHelper.ts b/src/utils/encryptionHelper.ts index 95a470a..6737056 100644 --- a/src/utils/encryptionHelper.ts +++ b/src/utils/encryptionHelper.ts @@ -32,7 +32,10 @@ export function createEncryptionKey(passphrase: string): Promise { }); } -export function encrypt(data: string, encryptionKey: string) { +export function encrypt( + data: string, + encryptionKey: string = process.env.ENCRYPTION_KEY as string, +) { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv( "aes-256-cbc", @@ -44,7 +47,10 @@ export function encrypt(data: string, encryptionKey: string) { return iv.toString("hex") + ":" + encrypted.toString("hex"); } -export function decrypt(encryptedData: string, encryptionKey: string) { +export function decrypt( + encryptedData: string, + encryptionKey: string = process.env.ENCRYPTION_KEY as string, +) { const [ivString, encryptedString] = encryptedData.split(":"); const iv = Buffer.from(ivString, "hex"); const encrypted = Buffer.from(encryptedString, "hex"); @@ -58,8 +64,8 @@ export function decrypt(encryptedData: string, encryptionKey: string) { return decrypted.toString(); } -const urlIV = crypto.randomBytes(16); -const urlKey = process.env.ENCRYPTION_KEY?.slice(0, 16) || "gudora-index9534"; +const urlKey = (process.env.ENCRYPTION_KEY as string).slice(0, 16); +const urlIV = Buffer.from(urlKey); export function urlEncrypt(fileId: string): string { const cipher = crypto.createCipheriv("aes-128-cbc", urlKey, urlIV); @@ -68,9 +74,9 @@ export function urlEncrypt(fileId: string): string { return cipherText; } -export function urlDecrypt(chiperText: string): string { +export function urlDecrypt(cipherText: string): string { const decipher = crypto.createDecipheriv("aes-128-cbc", urlKey, urlIV); - let plainText = decipher.update(chiperText, "hex", "utf8"); + let plainText = decipher.update(cipherText, "hex", "utf8"); plainText += decipher.final("utf8"); return plainText; -} \ No newline at end of file +} diff --git a/src/utils/jwtHelper.ts b/src/utils/jwtHelper.ts index 9f092bb..6e18229 100644 --- a/src/utils/jwtHelper.ts +++ b/src/utils/jwtHelper.ts @@ -1,6 +1,6 @@ import JWT from "jsonwebtoken"; -import { decrypt, encrypt } from "@utils/encryptionHelper"; -import { ExpiredJWTPayload, JWTPayload } from "@/types/jwt"; +import { decrypt, encrypt } from "utils/encryptionHelper"; +import { ExpiredJWTPayload, JWTPayload } from "types/jwt"; export function createJWTToken(payload: any, expiresIn: string = "3h") { return encrypt( diff --git a/src/utils/mimeTypesHelper.ts b/src/utils/mimeTypesHelper.ts index d8dc446..4cf954b 100644 --- a/src/utils/mimeTypesHelper.ts +++ b/src/utils/mimeTypesHelper.ts @@ -18,16 +18,15 @@ import { BsFileEarmarkWordFill, BsFileEarmarkZipFill, } from "react-icons/bs"; -import ModelPreview from "@components/FilePreview/ModelPreview"; -import AudioPreview from "@components/FilePreview/AudioPreview"; -import DefaultPreview from "@components/FilePreview/DefaultPreview"; -import MarkdownPreview from "@components/FilePreview/MarkdownPreview"; -import OfficePreview from "@components/FilePreview/OfficePreview"; -import PDFPreview from "@components/FilePreview/PDFPreview"; -import ImagePreview from "@components/FilePreview/ImagePreview"; -import CodePreview from "@components/FilePreview/CodePreview"; -import TextPreview from "@components/FilePreview/TextPreview"; -import VideoPreview from "@components/FilePreview/VideoPreview"; +import AudioPreview from "components/FilePreview/AudioPreview"; +import DefaultPreview from "components/FilePreview/DefaultPreview"; +import MarkdownPreview from "components/FilePreview/MarkdownPreview"; +import OfficePreview from "components/FilePreview/OfficePreview"; +import PDFPreview from "components/FilePreview/PDFPreview"; +import ImagePreview from "components/FilePreview/ImagePreview"; +import CodePreview from "components/FilePreview/CodePreview"; +import TextPreview from "components/FilePreview/TextPreview"; +import VideoPreview from "components/FilePreview/VideoPreview"; import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; diff --git a/src/utils/swrFetch.ts b/src/utils/swrFetch.ts index 8b8c273..189dc8c 100644 --- a/src/utils/swrFetch.ts +++ b/src/utils/swrFetch.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { FilesResponse } from "@/types/googleapis"; +import { FilesResponse } from "types/googleapis"; const fetcher = async (url: string, headers: Record = {}) => axios.get(url, { headers }).then((res) => res.data); diff --git a/tsconfig.json b/tsconfig.json index 6a372e7..399192c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,13 +14,15 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "baseUrl": "./src", "paths": { - "@/*": ["./src/*"], - "@components/*": ["./src/components/*"], - "@config/*": ["./src/config/*"], - "@hooks/*": ["./src/hooks/*"], - "@styles/*": ["./src/styles/*"], - "@utils/*": ["./src/utils/*"] +// "@/*": ["./src/*"], + "components/*": ["components/*"], + "config/*": ["config/*"], + "hooks/*": ["hooks/*"], + "styles/*": ["styles/*"], + "utils/*": ["utils/*"], + "types/*": ["types/*"], } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],