diff --git a/.gitignore b/.gitignore index bfc7379..2a4510c 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ next-env.d.ts /.next /.vscode /src/pages/api/legacy/ + +.vercel diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..1a2fb33 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/TODO.md b/TODO.md index 8630db6..3f02df2 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,5 @@ +- Redo how to handle error + # / - ~~Override opengraph using banner image~~ - ~~Render readme file~~ diff --git a/package.json b/package.json index 046370c..4c98133 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "googleapis": "^118.0.0", "jsonwebtoken": "^9.0.0", "mime-types": "^2.1.35", - "next": "^13.4.3", + "next": "^13.4.4", "next-seo": "^6.0.0", "nextjs-progressbar": "^0.0.16", "postcss": "8.4.22", diff --git a/src/app/(api)/api/banner/[encryptedFileId]/route.ts b/src/app/(api)/api/banner/[encryptedFileId]/route.ts index e69de29..7652871 100644 --- a/src/app/(api)/api/banner/[encryptedFileId]/route.ts +++ b/src/app/(api)/api/banner/[encryptedFileId]/route.ts @@ -0,0 +1,82 @@ +import { NextRequest, NextResponse } from "next/server"; + +import createErrorPayload from "utils/apiHelper/createErrorPayload"; +import gdrive from "utils/apiHelper/gdrive"; +import shortEncryption from "utils/encryptionHelper/shortEncryption"; +import ExtendedError from "utils/generalHelper/extendedError"; + +import { Constant } from "types/general/constant"; + +import apiConfig from "config/api.config"; + +export async function GET( + request: NextRequest, + { params }: { params: { encryptedFileId: string } }, +) { + const _start = Date.now(); + const { encryptedFileId } = params; + + try { + const getMetadata = gdrive.files.get({ + fileId: shortEncryption.decrypt(encryptedFileId), + fields: "id, name, mimeType, webContentLink", + }); + const getStream = gdrive.files.get( + { + fileId: shortEncryption.decrypt(encryptedFileId), + alt: "media", + }, + { responseType: "arraybuffer" }, + ); + + const [metadata, stream] = await Promise.all([ + getMetadata, + getStream, + ]); + + if (!metadata || metadata.data.trashed) { + throw new ExtendedError( + Constant.apiFileNotFound, + 404, + "notFound", + Constant.reasonNotFound, + ); + } + + if ( + Number(metadata.data.size) > + apiConfig.files.download.maxFileSize && + apiConfig.files.download.maxFileSize > 0 + ) { + return NextResponse.redirect( + metadata.data.webContentLink as string, + { + status: 302, + }, + ); + } + + const imgBuffer = (await stream.data) as ArrayBuffer; + + return new NextResponse(imgBuffer, { + status: 200, + headers: { + "Content-Type": + metadata.data.mimeType || + "application/octet-stream", + "Cache-Control": apiConfig.cacheControl, + "Content-Disposition": `inline; filename="${metadata.data.name}"`, + }, + }); + } catch (error: any) { + const payload = createErrorPayload( + error, + "GET /api/banner/:id", + _start, + ); + + return NextResponse.json(payload, { + status: payload.code, + }); + } +} diff --git a/src/app/(api)/api/files/[encryptedId]/route.ts b/src/app/(api)/api/files/[encryptedId]/route.ts index 13fd8e0..ab6e9af 100644 --- a/src/app/(api)/api/files/[encryptedId]/route.ts +++ b/src/app/(api)/api/files/[encryptedId]/route.ts @@ -45,78 +45,28 @@ export async function GET( }); if (!file || file.data.trashed) { - const msg = file.data.trashed - ? "File has been deleted" - : "File not found"; throw new ExtendedError( Constant.apiFileNotFound, 404, "notFound", - msg, + Constant.reasonNotFound, ); } + /** + * If fetched file isn't a folder, return File + */ if ( file.data.mimeType !== "application/vnd.google-apps.folder" ) { if (thumbnail === "1") { - if (!file.data.thumbnailLink) { - const imgStream = await fetch( - `${apiConfig.basePath}/og.png`, - ).then((res) => res.arrayBuffer()); - return new NextResponse(imgStream, { - status: 200, - }); - } - if (file.data.mimeType?.startsWith("image/")) { - const imgStream = await gdrive.files.get( - { - fileId: id, - alt: "media", - }, - { responseType: "stream" }, - ); - if ( - Number(file.data.size) < - apiConfig.files.download.maxFileSize && - apiConfig.files.download.maxFileSize > 0 - ) { - const arrayBuffer = - await new Promise( - (resolve, reject) => { - const chunks: Buffer[] = []; - imgStream.data.on("data", (chunk) => - chunks.push(chunk), - ); - imgStream.data.on("end", () => { - const buffer = Buffer.concat(chunks); - resolve( - buffer.buffer.slice( - buffer.byteOffset, - buffer.byteOffset + - buffer.byteLength, - ), - ); - }); - imgStream.data.on("error", reject); - }, - ); - return new NextResponse(arrayBuffer, { - status: 200, - headers: { - "Content-Type": - file.data.mimeType || - "application/octet-stream", - "Cache-Control": apiConfig.cacheControl, - "Content-Disposition": `inline; filename="${file.data.name}"`, - }, - }); - } - } - return NextResponse.redirect( - file.data.thumbnailLink as string, + `${ + apiConfig.basePath + }/api/thumbnail/${shortEncryption.encrypt( + id as string, + )}`, { status: 302, }, @@ -148,9 +98,8 @@ export async function GET( } const query = [ + ...apiConfig.files.query, `'${id}' in parents`, - "trashed = false", - "'me' in owners", ]; const fetchFolderContents = await gdrive.files.list({ q: `${query.join(" and ")}`, @@ -195,7 +144,7 @@ export async function GET( return NextResponse.redirect( `${ apiConfig.basePath - }/api/banner?id=${shortEncryption.encrypt( + }/api/banner/${shortEncryption.encrypt( bannerFile.id as string, )}`, { @@ -263,7 +212,7 @@ export async function GET( } catch (error: any) { const payload = createErrorPayload( error, - "GET /api/files", + "GET /api/files/:id", _start, ); diff --git a/src/app/(api)/api/files/route.ts b/src/app/(api)/api/files/route.ts index 84f718d..cc445bd 100644 --- a/src/app/(api)/api/files/route.ts +++ b/src/app/(api)/api/files/route.ts @@ -23,9 +23,8 @@ export async function GET(request: NextRequest) { ); const query: string[] = [ - "trashed = false", - "'me' in owners", - `parents = '${apiConfig.files.rootFolder}'`, + ...apiConfig.files.query, + `'${apiConfig.files.rootFolder}' in parents`, ]; const fetchFolderContents = await gdrive.files.list({ q: `${query.join(" and ")}`, @@ -70,7 +69,7 @@ export async function GET(request: NextRequest) { return NextResponse.redirect( `${ apiConfig.basePath - }/api/banner?id=${shortEncryption.encrypt( + }/api/banner/${shortEncryption.encrypt( bannerFile.id as string, )}`, { diff --git a/src/app/(api)/api/thumbnail/[encryptedFileId]/route.ts b/src/app/(api)/api/thumbnail/[encryptedFileId]/route.ts index e69de29..fbcd578 100644 --- a/src/app/(api)/api/thumbnail/[encryptedFileId]/route.ts +++ b/src/app/(api)/api/thumbnail/[encryptedFileId]/route.ts @@ -0,0 +1,103 @@ +import { NextRequest, NextResponse } from "next/server"; + +import createErrorPayload from "utils/apiHelper/createErrorPayload"; +import gdrive from "utils/apiHelper/gdrive"; +import shortEncryption from "utils/encryptionHelper/shortEncryption"; +import ExtendedError from "utils/generalHelper/extendedError"; + +import { Constant } from "types/general/constant"; + +import apiConfig from "config/api.config"; + +export async function GET( + request: NextRequest, + { params }: { params: { encryptedFileId: string } }, +) { + const _start = Date.now(); + const { encryptedFileId } = params; + + try { + const getMetadata = gdrive.files.get({ + fileId: shortEncryption.decrypt(encryptedFileId), + fields: + "id, name, mimeType, webContentLink, thumbnailLink, size", + }); + const getStream = gdrive.files.get( + { + fileId: shortEncryption.decrypt(encryptedFileId), + alt: "media", + }, + { responseType: "arraybuffer" }, + ); + + const [metadata, stream] = await Promise.all([ + getMetadata, + getStream, + ]); + + if (!metadata || metadata.data.trashed) { + throw new ExtendedError( + Constant.apiFileNotFound, + 404, + "notFound", + Constant.reasonNotFound, + ); + } + + if (!metadata.data.thumbnailLink) { + const imgStream = await fetch( + `${apiConfig.basePath}/og.png`, + ).then((res) => res.arrayBuffer()); + + return new NextResponse(imgStream, { + status: 200, + headers: { + "Content-Type": + metadata.data.mimeType || + "application/octet-stream", + "Cache-Control": apiConfig.cacheControl, + "Content-Disposition": `inline; filename="${metadata.data.name}"`, + }, + }); + } + + const isWithinMaxFileSize = + Number(metadata.data.size) < + apiConfig.files.download.maxFileSize && + apiConfig.files.download.maxFileSize > 0; + + if ( + metadata.data.mimeType?.startsWith("image") && + isWithinMaxFileSize + ) { + const imgBuffer = (await stream.data) as ArrayBuffer; + + return new NextResponse(imgBuffer, { + status: 200, + headers: { + "Cache-Control": apiConfig.cacheControl, + }, + }); + } + + return NextResponse.redirect( + metadata.data.thumbnailLink as string, + { + status: 302, + headers: { + "Cache-Control": apiConfig.cacheControl, + }, + }, + ); + } catch (error: any) { + const payload = createErrorPayload( + error, + "GET /api/thumbnail/:id", + _start, + ); + + return NextResponse.json(payload, { + status: payload.code, + }); + } +} diff --git a/src/app/[...path]/page.tsx b/src/app/[...path]/page.tsx index af8911f..4e0cfdc 100644 --- a/src/app/[...path]/page.tsx +++ b/src/app/[...path]/page.tsx @@ -120,7 +120,6 @@ async function FilePage({ params }: Props) { if (!pathValidation.success) { const errorData = pathValidation as API_Error; - console.error(errorData); const payload = handleError(errorData); throw new Error(payload); } diff --git a/src/app/error.tsx b/src/app/error.tsx index fac58bc..c678541 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -24,24 +24,23 @@ export default function Error({ const [path, setPath] = useState("root"); useEffect(() => { - console.log("CHECKPOINT ERROR PAGE", error); if (error.message.includes("{")) { - console.log("CHECKPOINT ERROR MESSAGE IS JSON"); const errorObj = JSON.parse( error.message, ) as ExtendedError; const extendError = new ExtendedError( - errorObj.extendedMessage || + errorObj.extendedMessage ?? Constant.apiInternalError, - errorObj.code || 500, - errorObj.category || "internalServerError", - errorObj.reason || "Internal Server Error", + errorObj.code ?? 500, + errorObj.category ?? "internalServerError", + errorObj.reason ?? "Internal Server Error", ); - console.log("CHECKPOINT EXTENDED ERROR", extendError); setExtendedError(extendError); + console.log(extendError.code); + const path = - extendError.reason?.split('"')[1].split('"')[0] || + extendError.reason?.split('"')[1]?.split('"')[0] ?? "root"; setPath(path); } else { @@ -55,14 +54,14 @@ export default function Error({ } }, [error]); - useEffect(() => { - if (extendedError?.code === 401) { - router.push( - `/password?redirect=${pathname}&path=${path}`, - ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [extendedError, path, pathname]); + // useEffect(() => { + // if (extendedError?.code === 401) { + // router.push( + // `/password?redirect=${pathname}&path=${path}`, + // ); + // } + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [extendedError, path, pathname]); return (
+ /> +
); } diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 89048af..fe138f8 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,7 +1,6 @@ import Link from "next/link"; function NotFound() { - console.log("Not Found"); return (