mirror of
https://github.com/Nezumi-2711/next-gdrive-index.git
synced 2026-09-22 13:38:38 +00:00
Remove unused folder
This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
import { ErrorResponse, FileResponse, TFileParent } from "@/types/googleapis";
|
||||
import drive from "@utils/driveClient";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import config from "@config/site.config";
|
||||
import { validateProtected } from "@utils/driveHelper";
|
||||
import { ExtendedError } from "@/types/default";
|
||||
import { reverseString } from "@utils/hashHelper";
|
||||
import initMiddleware from "@utils/apiMiddleware";
|
||||
|
||||
async function handler(request: NextApiRequest, response: NextApiResponse) {
|
||||
try {
|
||||
const { id, hash } = request.query;
|
||||
const { authorization } = request.headers;
|
||||
const headerHash = authorization?.split(" ")[1] || null;
|
||||
|
||||
const fetchFileMetadata = await drive.files.get({
|
||||
fileId: id as string,
|
||||
fields: "id, name, mimeType, size, exportLinks, parents",
|
||||
});
|
||||
|
||||
if (!config.files.allowDownloadProtectedWithoutAccess) {
|
||||
const parentsArray: TFileParent[] = [];
|
||||
|
||||
let validHash = headerHash as string;
|
||||
if (hash) {
|
||||
validHash = reverseString(hash as string);
|
||||
}
|
||||
// Fetch parents
|
||||
if (
|
||||
fetchFileMetadata.data.mimeType === "application/vnd.google-apps.folder"
|
||||
) {
|
||||
parentsArray.push({
|
||||
id: fetchFileMetadata.data.id as string,
|
||||
name: fetchFileMetadata.data.name as string,
|
||||
});
|
||||
}
|
||||
let parents = fetchFileMetadata.data.parents || [];
|
||||
while (parents.length > 0) {
|
||||
const fetchParents = await drive.files.get({
|
||||
fileId: parents[0],
|
||||
fields: "id, name, parents",
|
||||
});
|
||||
if (fetchParents.data.id === config.files.rootFolder) {
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
break;
|
||||
}
|
||||
parents = fetchParents.data.parents || [];
|
||||
if (!parents.length) break;
|
||||
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
}
|
||||
|
||||
// Check for password file
|
||||
const validatePassword = await validateProtected(parentsArray, validHash);
|
||||
if (validatePassword.isProtected && !validatePassword.valid) {
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
passwordValidated: false,
|
||||
parents: [],
|
||||
file: {},
|
||||
} as FileResponse);
|
||||
}
|
||||
}
|
||||
|
||||
const { name, mimeType, size } = fetchFileMetadata.data;
|
||||
|
||||
if (mimeType === "application/vnd.google-apps.folder") {
|
||||
const error = new Error("Folder cannot be downloaded") as ExtendedError;
|
||||
error.cause = "badRequest";
|
||||
error.code = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
response.setHeader(
|
||||
"Content-Disposition",
|
||||
`attachment; filename=${encodeURIComponent(name as string)}`,
|
||||
);
|
||||
response.setHeader("Content-Type", mimeType || "application/octet-stream");
|
||||
response.setHeader("Content-Length", size || 0);
|
||||
|
||||
const streamFile = await drive.files.get(
|
||||
{
|
||||
fileId: id as string,
|
||||
alt: "media",
|
||||
},
|
||||
{
|
||||
responseType: "stream",
|
||||
},
|
||||
);
|
||||
|
||||
return response.send(streamFile.data);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
export default initMiddleware(handler);
|
||||
@@ -1,171 +0,0 @@
|
||||
import {
|
||||
ErrorResponse,
|
||||
FileResponse,
|
||||
FilesResponse,
|
||||
TFileParent,
|
||||
} from "types/googleapis";
|
||||
import drive from "utils/driveClient";
|
||||
import {} from "utils/driveHelper";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import config from "config/site.config";
|
||||
|
||||
export default async function handler(
|
||||
request: NextApiRequest,
|
||||
response: NextApiResponse<FilesResponse | FileResponse | ErrorResponse>,
|
||||
) {
|
||||
try {
|
||||
const _start = Date.now();
|
||||
const { id } = request.query;
|
||||
const { authorization } = request.headers;
|
||||
const hash = authorization?.split(" ")[1] || null;
|
||||
|
||||
const parentsArray: TFileParent[] = [];
|
||||
|
||||
const fetchFile = await drive.files.get({
|
||||
fileId: id as string,
|
||||
fields:
|
||||
"id, name, mimeType, parents, thumbnailLink, fileExtension, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, exportLinks",
|
||||
});
|
||||
|
||||
// Fetch parents
|
||||
if (fetchFile.data.mimeType === "application/vnd.google-apps.folder") {
|
||||
parentsArray.push({
|
||||
id: fetchFile.data.id as string,
|
||||
name: fetchFile.data.name as string,
|
||||
});
|
||||
}
|
||||
let parents = fetchFile.data.parents || [];
|
||||
while (parents.length > 0) {
|
||||
const fetchParents = await drive.files.get({
|
||||
fileId: parents[0],
|
||||
fields: "id, name, parents",
|
||||
});
|
||||
if (fetchParents.data.id === config.files.rootFolder) {
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
break;
|
||||
}
|
||||
parents = fetchParents.data.parents || [];
|
||||
if (!parents.length) break;
|
||||
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
}
|
||||
|
||||
// Check for password file
|
||||
const validatePassword = await validateProtected(
|
||||
parentsArray || (id as string),
|
||||
hash as string,
|
||||
);
|
||||
if (validatePassword.isProtected && !validatePassword.valid) {
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
passwordValidated: false,
|
||||
protectedId: validatePassword.protectedId,
|
||||
parents: [],
|
||||
file: {},
|
||||
} as FileResponse);
|
||||
}
|
||||
|
||||
// Check if file is folder
|
||||
if (fetchFile.data.mimeType === "application/vnd.google-apps.folder") {
|
||||
const { pageToken } = request.query;
|
||||
|
||||
const fetchFiles = await drive.files.list({
|
||||
q: buildQuery({
|
||||
id: id as string,
|
||||
extraQuery: ["not mimeType contains 'application/vnd.google-apps'"],
|
||||
}),
|
||||
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 fetchFolders = await drive.files.list({
|
||||
q: buildQuery({
|
||||
id: id as string,
|
||||
extraQuery: ["mimeType = 'application/vnd.google-apps.folder'"],
|
||||
}),
|
||||
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 checkReadme = await drive.files.list({
|
||||
q: buildQuery({ id: id as string, extraQuery: ["name = 'readme.md'"] }),
|
||||
});
|
||||
|
||||
const folders =
|
||||
fetchFolders.data.files?.filter(
|
||||
(file) => file.mimeType === "application/vnd.google-apps.folder",
|
||||
) || [];
|
||||
const files =
|
||||
fetchFiles.data.files?.filter(
|
||||
(file) => file.mimeType !== "application/vnd.google-apps.folder",
|
||||
) || [];
|
||||
|
||||
const payload: FilesResponse = {
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
parents: parentsArray,
|
||||
passwordRequired: validatePassword.isProtected,
|
||||
passwordValidated: validatePassword.valid,
|
||||
protectedId: validatePassword.protectedId,
|
||||
folders,
|
||||
files,
|
||||
nextPageToken: fetchFiles.data.nextPageToken || undefined,
|
||||
readmeExists: !!checkReadme.data.files?.length,
|
||||
};
|
||||
|
||||
return response.status(200).json(payload);
|
||||
}
|
||||
|
||||
const payload: FileResponse = {
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
parents: parentsArray,
|
||||
passwordRequired: validatePassword.isProtected,
|
||||
passwordValidated: validatePassword.valid,
|
||||
protectedId: validatePassword.protectedId,
|
||||
file: fetchFile.data,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
import { ErrorResponse, FileResponse, TFileParent } from "@/types/googleapis";
|
||||
import drive from "@utils/driveClient";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import config from "@config/site.config";
|
||||
import { validateProtected } from "@utils/driveHelper";
|
||||
import { ExtendedError } from "@/types/default";
|
||||
import { reverseString } from "@utils/hashHelper";
|
||||
|
||||
export default async function handler(
|
||||
request: NextApiRequest,
|
||||
response: NextApiResponse,
|
||||
) {
|
||||
try {
|
||||
const { id, hash } = request.query;
|
||||
const { authorization } = request.headers;
|
||||
const headerHash = authorization?.split(" ")[1] || null;
|
||||
|
||||
const fetchFileMetadata = await drive.files.get({
|
||||
fileId: id as string,
|
||||
fields: "id, name, mimeType, size, exportLinks, parents",
|
||||
});
|
||||
|
||||
if (!config.files.allowDownloadProtectedWithoutAccess) {
|
||||
const parentsArray: TFileParent[] = [];
|
||||
|
||||
let validHash = headerHash as string;
|
||||
if (hash) {
|
||||
validHash = reverseString(hash as string);
|
||||
}
|
||||
|
||||
// Fetch parents
|
||||
if (
|
||||
fetchFileMetadata.data.mimeType === "application/vnd.google-apps.folder"
|
||||
) {
|
||||
parentsArray.push({
|
||||
id: fetchFileMetadata.data.id as string,
|
||||
name: fetchFileMetadata.data.name as string,
|
||||
});
|
||||
}
|
||||
let parents = fetchFileMetadata.data.parents || [];
|
||||
while (parents.length > 0) {
|
||||
const fetchParents = await drive.files.get({
|
||||
fileId: parents[0],
|
||||
fields: "id, name, parents",
|
||||
});
|
||||
if (fetchParents.data.id === config.files.rootFolder) {
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
break;
|
||||
}
|
||||
parents = fetchParents.data.parents || [];
|
||||
if (!parents.length) break;
|
||||
|
||||
parentsArray.push({
|
||||
id: fetchParents.data.id as string,
|
||||
name: fetchParents.data.name as string,
|
||||
});
|
||||
}
|
||||
|
||||
// Check for password file
|
||||
const validatePassword = await validateProtected(parentsArray, validHash);
|
||||
if (validatePassword.isProtected && !validatePassword.valid) {
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
passwordValidated: false,
|
||||
parents: [],
|
||||
file: {},
|
||||
} as FileResponse);
|
||||
}
|
||||
}
|
||||
|
||||
const { name, mimeType, size } = fetchFileMetadata.data;
|
||||
|
||||
if (mimeType === "application/vnd.google-apps.folder") {
|
||||
const error = new Error("Folder cannot be downloaded") as ExtendedError;
|
||||
error.cause = "badRequest";
|
||||
error.code = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
response.setHeader(
|
||||
"Content-Disposition",
|
||||
`inline; filename=${encodeURIComponent(name as string)}`,
|
||||
);
|
||||
response.setHeader("Content-Type", mimeType || "application/octet-stream");
|
||||
response.setHeader("Content-Length", size || 0);
|
||||
|
||||
const streamFile = await drive.files.get(
|
||||
{
|
||||
fileId: id as string,
|
||||
alt: "media",
|
||||
},
|
||||
{
|
||||
responseType: "stream",
|
||||
},
|
||||
);
|
||||
|
||||
return response.send(streamFile.data);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
import { ErrorResponse, FilesResponse } from "@/types/googleapis";
|
||||
import drive from "@/utils/driveClient";
|
||||
import { buildQuery, validateProtected } from "@/utils/driveHelper";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import config from "@config/site.config";
|
||||
|
||||
export default async function handler(
|
||||
request: NextApiRequest,
|
||||
response: NextApiResponse<FilesResponse | ErrorResponse>,
|
||||
) {
|
||||
try {
|
||||
const { pageToken } = request.query;
|
||||
const { authorization } = request.headers;
|
||||
const hash = authorization?.split(" ")[1] || null;
|
||||
|
||||
// Check for password file
|
||||
const validatePassword = await validateProtected(
|
||||
config.files.rootFolder,
|
||||
hash as string,
|
||||
);
|
||||
if (validatePassword.isProtected && !validatePassword.valid) {
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: true,
|
||||
passwordValidated: false,
|
||||
protectedId: config.files.rootFolder,
|
||||
parents: [],
|
||||
files: [],
|
||||
folders: [],
|
||||
nextPageToken: undefined,
|
||||
readmeExists: false,
|
||||
});
|
||||
}
|
||||
|
||||
const fetchFiles = await drive.files.list({
|
||||
q: buildQuery({
|
||||
extraQuery: ["not mimeType contains 'application/vnd.google-apps'"],
|
||||
}),
|
||||
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 fetchFolders = await drive.files.list({
|
||||
q: buildQuery({
|
||||
extraQuery: ["mimeType = 'application/vnd.google-apps.folder'"],
|
||||
}),
|
||||
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 checkReadme = await drive.files.list({
|
||||
q: buildQuery({ extraQuery: ["name = 'readme.md'"] }),
|
||||
});
|
||||
|
||||
const folders =
|
||||
fetchFolders.data.files?.filter(
|
||||
(file) => file.mimeType === "application/vnd.google-apps.folder",
|
||||
) || [];
|
||||
const files =
|
||||
fetchFiles.data.files?.filter(
|
||||
(file) => file.mimeType !== "application/vnd.google-apps.folder",
|
||||
) || [];
|
||||
|
||||
const payload: FilesResponse = {
|
||||
success: true,
|
||||
timestamp: new Date().toISOString(),
|
||||
passwordRequired: validatePassword.isProtected,
|
||||
passwordValidated: validatePassword.valid,
|
||||
protectedId: validatePassword.protectedId,
|
||||
folders,
|
||||
files,
|
||||
nextPageToken: fetchFiles.data.nextPageToken || undefined,
|
||||
readmeExists: !!checkReadme.data.files?.length,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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<SearchResponse | ErrorResponse>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import { promisify } from "util";
|
||||
import { pipeline } from "stream";
|
||||
import { GetServerSideProps } from "next";
|
||||
import drive from "utils/driveClient";
|
||||
|
||||
export default function Media() {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
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,
|
||||
]);
|
||||
// Only allow images, video, audio, and pdf
|
||||
if (
|
||||
!(imageMetadata.mimeType as string).startsWith("image/") &&
|
||||
!(imageMetadata.mimeType as string).startsWith("video/") &&
|
||||
!(imageMetadata.mimeType as string).startsWith("audio/")
|
||||
) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
// Check fileName === image metadata
|
||||
if (fileName !== imageMetadata.name) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
res.setHeader("Content-Type", imageMetadata.mimeType as string);
|
||||
await pipelineAsync(imageStream.data, res);
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
};
|
||||
@@ -1,146 +0,0 @@
|
||||
import { MdWarning } from "react-icons/md";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
createEncryptionKey,
|
||||
generateRandomEncryptionKey,
|
||||
} from "utils/encryptionHelper";
|
||||
import useCopyText from "hooks/useCopyText";
|
||||
import { toast } from "react-toastify";
|
||||
import Link from "next/link";
|
||||
import useLocalStorage from "hooks/useLocalStorage";
|
||||
|
||||
export default function Encryption() {
|
||||
const [settingJson, setSettingJson] = useLocalStorage("tempEncryption", "");
|
||||
const [key, setKey] = useState<string>("");
|
||||
const [allowNext, setAllowNext] = useState<boolean>(false);
|
||||
const copyText = useCopyText();
|
||||
|
||||
useEffect(() => {
|
||||
if (settingJson) {
|
||||
setKey(settingJson);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (key) {
|
||||
setAllowNext(true);
|
||||
} else {
|
||||
setAllowNext(false);
|
||||
}
|
||||
}, [key]);
|
||||
|
||||
return (
|
||||
<div className='mx-auto flex max-w-screen-xl flex-col gap-4'>
|
||||
<div className={"card"}>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Encryption key</span>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div className={"banner warning"}>
|
||||
<MdWarning className={"h-6 w-6 text-red-500"} />
|
||||
<span>
|
||||
Make sure you don't share your encryption key with anyone.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className={"mx-auto max-w-screen-md px-4 py-4 text-center"}>
|
||||
On this page you can generate a random encryption key, or you can
|
||||
define your own.
|
||||
<br />
|
||||
This key will be used to encrypt your files.
|
||||
<br />
|
||||
<br />
|
||||
You can also copy the key to your clipboard and save it somewhere
|
||||
safe.
|
||||
</p>
|
||||
|
||||
<div
|
||||
className={"mx-auto flex w-full max-w-screen-md flex-col gap-4 px-4"}
|
||||
>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<span className='font-bold'>Encryption key</span>
|
||||
<input
|
||||
type={"text"}
|
||||
className={"pr-4"}
|
||||
value={key}
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
placeholder={"Enter your encryption key here..."}
|
||||
/>
|
||||
</div>
|
||||
<div className={"mx-auto grid w-full max-w-md grid-cols-2 gap-2"}>
|
||||
<button
|
||||
className={"primary flex w-full items-center justify-center"}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
generateRandomEncryptionKey()
|
||||
.then((key) => {
|
||||
setKey(key);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error("Failed to generate encryption key");
|
||||
console.error(err);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Generate
|
||||
</button>
|
||||
<button
|
||||
className={"danger flex w-full items-center justify-center"}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
setKey("");
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
className={
|
||||
"secondary col-span-full flex w-full items-center justify-center"
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
copyText(key);
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div
|
||||
className={
|
||||
"mx-auto flex w-full items-center justify-end gap-2 tablet:gap-4"
|
||||
}
|
||||
>
|
||||
<Link href={"/setup"}>
|
||||
<button className={"secondary"}>Previous Page</button>
|
||||
</Link>
|
||||
<Link
|
||||
href={allowNext ? "/setup/google-cloud" : ""}
|
||||
onClick={async () => {
|
||||
if (!allowNext) return;
|
||||
if (!key) return;
|
||||
createEncryptionKey(key).then((encryptionKey) => {
|
||||
setSettingJson(encryptionKey);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className={"primary"}
|
||||
disabled={!allowNext}
|
||||
>
|
||||
Next Page
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import LoadingFeedback from "components/APIFeedback/Loading";
|
||||
import MarkdownRender from "components/utility/MarkdownRender";
|
||||
import useLocalStorage from "hooks/useLocalStorage";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type ConfigProps = {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
refresh_token: string;
|
||||
};
|
||||
const defaultConfig: ConfigProps = {
|
||||
client_id: "",
|
||||
client_secret: "",
|
||||
refresh_token: "",
|
||||
};
|
||||
|
||||
export default function SetupFinal() {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [tempKey] = useLocalStorage("tempEncryption", "");
|
||||
const [tempConfig] = useLocalStorage<ConfigProps>(
|
||||
"tempGoogleCloud",
|
||||
defaultConfig,
|
||||
);
|
||||
const [dataConfig, setDataConfig] = useState<ConfigProps>(defaultConfig);
|
||||
const [dataKey, setDataKey] = useState<string>("");
|
||||
const [mdContent, setMdContent] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
if (tempConfig) {
|
||||
setDataConfig(tempConfig);
|
||||
}
|
||||
if (tempKey) {
|
||||
setDataKey(tempKey);
|
||||
}
|
||||
setIsLoading(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
const mdContent = `To finishing the setup, you need to do the following steps:
|
||||
|
||||
## API Config
|
||||
The API Config file are located at \`src/config/api.ts\`. You need to fill in the following information:
|
||||
\`\`\`js
|
||||
module.exports = {
|
||||
client_id: "${dataConfig.client_id}",
|
||||
client_secret: "${dataConfig.client_secret}",
|
||||
refresh_token: "${dataConfig.refresh_token}",
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## Environment Config
|
||||
The environment variables are differ for each hosting service. If you are using Vercel, you can add the environment variables on the project settings page.
|
||||
\`\`\`
|
||||
ENCRYPTION_KEY="${dataKey}"
|
||||
\`\`\`
|
||||
|
||||
**NOTE:** Make sure to redeploy the site after adding the environment variables.
|
||||
|
||||
## Customizing the Site
|
||||
The site can be customized by editing the \`src/config/site.ts\` file. You can change the site title, description, and other information.
|
||||
All the explanation also included in the file.`;
|
||||
|
||||
setMdContent(mdContent);
|
||||
setIsLoading(false);
|
||||
|
||||
// Remove the temp data
|
||||
localStorage.removeItem("tempEncryption");
|
||||
localStorage.removeItem("tempGoogleCloud");
|
||||
}, [dataConfig, dataKey]);
|
||||
|
||||
return (
|
||||
<div className={"mx-auto flex max-w-screen-xl flex-col gap-4"}>
|
||||
<div className={"card"}>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Finishing setup</span>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
{isLoading ? (
|
||||
<LoadingFeedback message={"Loading data..."} />
|
||||
) : (
|
||||
<MarkdownRender content={mdContent} />
|
||||
)}
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div
|
||||
className={
|
||||
"mx-auto flex w-full flex-row items-center justify-end gap-2 tablet:gap-4"
|
||||
}
|
||||
>
|
||||
<Link href={"/"}>
|
||||
<button className={"primary"}>Finish</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { encrypt } from "utils/encryptionHelper";
|
||||
import Link from "next/link";
|
||||
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";
|
||||
|
||||
export default function SetupGoogleCloud() {
|
||||
const [encryptionKey] = useLocalStorage("tempEncryption", "");
|
||||
const [_settingJson, setSettingJson] = useLocalStorage("tempGoogleCloud", {
|
||||
client_id: "",
|
||||
client_secret: "",
|
||||
refresh_token: "",
|
||||
});
|
||||
const [client_id, setClientID] = useState<string>("");
|
||||
const [client_secret, setClientSecret] = useState<string>("");
|
||||
const [refresh_token, setRefreshToken] = useState<string>("");
|
||||
const [allowNext, setAllowNext] = useState<boolean>(false);
|
||||
|
||||
const { data, isLoading } = useSWR("/setup/GoogleCloudStep.md", fetcher);
|
||||
|
||||
useEffect(() => {
|
||||
if (client_id && client_secret && refresh_token) {
|
||||
setAllowNext(true);
|
||||
} else {
|
||||
setAllowNext(false);
|
||||
}
|
||||
}, [client_id, client_secret, refresh_token]);
|
||||
|
||||
return (
|
||||
<div className='mx-auto flex max-w-screen-xl flex-col gap-4'>
|
||||
<div className={"card"}>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Setting up Google Cloud</span>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div className={"banner"}>
|
||||
<span>
|
||||
If you already have Client ID, Client Secret, and Refresh Token.
|
||||
Click <a href={"#skip"}>here</a> to skip this step.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={"mx-auto flex w-full max-w-screen-md flex-col gap-4 px-4"}
|
||||
>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
{isLoading ? (
|
||||
<LoadingFeedback message={"Loading file..."} />
|
||||
) : (
|
||||
<MarkdownRender content={data as string} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={"card"}
|
||||
id={"skip"}
|
||||
>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Store Google Cloud credentials</span>
|
||||
</div>
|
||||
<div className={"divider-horizontal"} />
|
||||
<div
|
||||
className={"mx-auto flex w-full max-w-screen-md flex-col gap-4 px-4"}
|
||||
>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<span className='font-bold'>Client ID</span>
|
||||
<input
|
||||
type={"text"}
|
||||
className={"pr-4"}
|
||||
value={client_id}
|
||||
onChange={(e) => setClientID(e.target.value)}
|
||||
placeholder={"Enter your client id here..."}
|
||||
/>
|
||||
</div>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<span className='font-bold'>Client Secret</span>
|
||||
<input
|
||||
type={"text"}
|
||||
className={"pr-4"}
|
||||
value={client_secret}
|
||||
onChange={(e) => setClientSecret(e.target.value)}
|
||||
placeholder={"Enter your client secret here..."}
|
||||
/>
|
||||
<span className={"text-xs tablet:text-sm"}>
|
||||
* Client secret will be encrypted using your encryption key
|
||||
</span>
|
||||
</div>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<span className='font-bold'>Refresh Token</span>
|
||||
<input
|
||||
type={"text"}
|
||||
className={"pr-4"}
|
||||
value={refresh_token}
|
||||
onChange={(e) => setRefreshToken(e.target.value)}
|
||||
placeholder={"Enter your refresh token here..."}
|
||||
/>
|
||||
<span className={"text-xs tablet:text-sm"}>
|
||||
* Refresh token will be encrypted using your encryption key
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div
|
||||
className={
|
||||
"mx-auto flex w-full items-center justify-end gap-2 tablet:gap-4"
|
||||
}
|
||||
>
|
||||
<Link href={"/setup/encryption"}>
|
||||
<button className={"secondary"}>Previous Page</button>
|
||||
</Link>
|
||||
<Link
|
||||
href={allowNext ? "/setup/final" : ""}
|
||||
onClick={() => {
|
||||
if (!allowNext) return;
|
||||
setSettingJson({
|
||||
client_id,
|
||||
client_secret: encrypt(client_secret, encryptionKey),
|
||||
refresh_token: encrypt(refresh_token, encryptionKey),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className={"primary"}
|
||||
disabled={!allowNext}
|
||||
>
|
||||
Next Page
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Setup() {
|
||||
return (
|
||||
<div className={"mx-auto flex max-w-screen-xl flex-col gap-4"}>
|
||||
<div className={"card"}>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Starting configuration</span>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<p className={"mx-auto max-w-screen-md px-4 py-4 text-center"}>
|
||||
This page will guide you through the initial configuration for
|
||||
deploying guDora-index. It start from setting up your encryption key,
|
||||
Google cloud, and setting up the project.
|
||||
<br />
|
||||
<br />
|
||||
This step will take you couple of minutes. So please read the
|
||||
instructions and follow them carefully.
|
||||
</p>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div
|
||||
className={
|
||||
"mx-auto flex w-full flex-row items-center justify-end gap-2 tablet:gap-4"
|
||||
}
|
||||
>
|
||||
<Link href={"/setup/encryption"}>
|
||||
<button className={"primary"}>Start</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user