diff --git a/src/lib/configurationHelper.ts b/src/lib/configurationHelper.ts new file mode 100644 index 0000000..eec36b1 --- /dev/null +++ b/src/lib/configurationHelper.ts @@ -0,0 +1,321 @@ +import { z } from "zod"; + +const configurationTemplate = `import { type z } from "zod"; +import { BASE_URL } from "~/constant"; + +import { type Schema_Config } from "~/types/schema"; + +const config: z.input = { + /** + * If possible, please don't change this value + * Even if you're creating a PR, just let me change it myself + */ + version: "{{ version }}", + /** + * Base path of the app, used for generating links + * + * If you're using another port for development, you can set it here + * + * @default process.env.NEXT_PUBLIC_DOMAIN + * @fallback process.env.NEXT_PUBLIC_VERCEL_URL + */ + basePath: new URL(BASE_URL).toString(), + + /** + * Show deploy guide dropdown on navbar + * that contains the deploy guide and configurator + * + * Set this to false on final deployment, except you want to show it + * + * @default false + */ + showGuideButton: {{ showGuideButton }}, + + /** + * How long the cache will be stored in the browser + * Used for all pages and api routes + * + * @default "public, max-age=60, s-maxage=60, stale-while-revalidate" + */ + cacheControl: "{{ cacheControl }}", + + apiConfig: { + /** + * Starting point of the drive. + * Will be used for '/' route. + * + * Since service account can't access 'root' folder + * You need to create a new folder and share it with the service account + * Then, copy the folder id and paste it here + */ + rootFolder: + "{{ api.rootFolder }}", + + /** + * If your rootfolder inside a shared drive, you NEED to set this to true + * If not, you can set this to false + * + * You also need to set the shared drive ID to make it work + * Make sure you have add your service account to the shared drive since the service account can't access the shared drive by default + * + * Where to get the shared drive id? + * Go to your Shared Drive > Click on the shared drive > copy the ID from the url + * ex: https://drive.google.com/drive/u/0/folders/:shared_drive_id + * + * Then you need to encrypt it using \`/api/internal/encrypt?q=:shared_drive_id\` route + */ + isTeamDrive: {{ api.isTeamDrive }}, + sharedDrive: "{{ api.sharedDrive }}", + + defaultQuery: ["trashed = false", "(not mimeType contains 'google-apps' or mimeType contains 'folder')"], + defaultField: + "id, name, mimeType, thumbnailLink, fileExtension, modifiedTime, size, imageMediaMetadata, videoMediaMetadata, webContentLink, trashed", + defaultOrder: "folder, name asc, modifiedTime desc", + itemsPerPage: {{ api.itemsPerPage }}, + searchResult: {{ api.searchResult }}, + + /** + * Special file name that will be used for certain purposes + * These files will be ignored when searching for files + * and will be hidden from the files list by default + */ + specialFile: { + password: "{{ api.specialFile.password }}", + readme: "{{ api.specialFile.readme }}", + /** + * Banner will be used for opengraph image for folder + * By default, all folder will use default og image + */ + banner: "{{ api.specialFile.banner }}", + }, + /** + * Reason why banner has multiple extensions: + * - If I use contains query, it will also match the file or folder that contains the word. + * (e.g: File / folder with the name of "Test Password" will be matched) + * - If I use = query, it will only match the exact name, hence the multiple extensions + * + * You can add more extensions if you want + */ + hiddenFiles: {{ api.hiddenFiles }}, + + /** + * By default, the app will use the thumbnail URL from Google Drive + * + * Sometimes, the thumbnail can't be accessed because of CORS policy + * If you're having this issue, you can set this to true + * + * This will make the api fetch the thumbnail and serve it from the server + * instead of using the Google Drive thumbnail + * + * This will increase the server load, so use it wisely + * + * Default: true + */ + proxyThumbnail: {{ api.proxyThumbnail }}, + + /** + * Only show preview for files that are smaller than this size + * If the file is larger than this size, it will show "can't preview" message instead + * + * Why? + * Since the stream endpoint are counted as a bandwidth usage + * I want to limit the preview to only small files + * It also to prevent abuse from the user + * + * You can also set this to 0 to disable the limit + * + * Default: 100MB + */ + streamMaxSize: {{ api.streamMaxSize }}, + + /** + * Maximum file size that can be downloaded via api routes + * If it's larger than this, it will be redirected to the file url + * + * If you're using Vercel, they have a limit of ~4 - ~4.5MB response size + * ref: https://vercel.com/docs/functions/runtimes#request-body-size + * If you're using another platform, you can match the limit with your platform + * Or you can set this to 0 to disable the limit + * + * Default: 4MB + */ + maxFileSize: {{ api.maxFileSize }}, + + /** + * Allow user to download protected file without password. + * If this set to false, download link will have temporary token attached to it + * If this set to true, user can download the file without password as long as they have the link + * + * Default: false + */ + allowDownloadProtectedFile: {{ api.allowDownloadProtectedFile }}, + + /** + * Duration in hours. + * In version 2, this will be used for download link expiration. + * If you need it under 1 hour, you can use math expression. (e.g: (5 / 60) * 1 = 5 minutes) + * + * This only affect when the user download the file + * For example if you set it for example 30 minutes (0.5) + * After 30 minutes, and the user still downloading the file, the download will NOT be interrupted + * But if the user refresh the page / trying to download again, the download link will be expired + * + * Default: 1 hour + */ + temporaryTokenDuration: {{ api.temporaryTokenDuration }}, + }, + + siteConfig: { + /** + * Site Name will be used for default metadata title + * Site Name Template will be used if the page has a title + * %s will be replaced with the page title + * %t will be replaced with the site name + * + * You can set it to undefined if you don't want to use it + */ + siteName: "{{ site.siteName }}", + siteNameTemplate: "{{ site.siteNameTemplate }}", + siteDescription: "{{ site.siteDescription }}", + siteIcon: "/logo.svg", + siteAuthor: "{{ site.siteAuthor }}", + favIcon: "/favicon.png", + /** + * Next.js Metadata robots object + * + * ref: https://nextjs.org/docs/app/api-reference/functions/generate-metadata#robots + */ + robots: "{{ site.robots }}", + twitterHandle: "{{ site.twitterHandle }}", + + /** + * Show file extension on the file name + * Example: + * true | false + * file.txt | file + * 100KB | txt / 100KB + * + * Default: false + */ + showFileExtension: {{ site.showFileExtension }}, + + /** + * Site wide password protection + * If this is set, all files and folders will be protected by this password + * + * The site password are set from Environment Variable (NEXT_GDRIVE_INDEX_PASSWORD) + * It's because I don't want to store sensitive data in the code + */ + privateIndex: {{ site.privateIndex }}, + + /** + * Maximum breadcrumb length + * If the breadcrumb is longer than this, it will be shortened + */ + breadcrumbMax: {{ site.breadcrumbMax }}, + + /** + * Toast notification configuration + * + * position: Self-explanatory + * duration: duration before the toast disappear in milliseconds + */ + toaster: { + position: "{{ site.toaster.position }}", + duration: {{ site.toaster.duration }}, + }, + + /** + * Configuration for file preview + */ + previewSettings: { + manga: { + /** + * Load first X MB of the file for preview + * or load first X items for preview + * + * @default + * maxSize: 15MB + * maxItem: 10 items + */ + maxSize: 15 * 1024 * 1024, + maxItem: 10, + }, + }, + + /** + * Example item: + * { + * icon: string, // icon name from lucide icons (https://lucide.dev/icons/) + * name: string, + * href: string, + * external?: boolean + * } + */ + navbarItems: {{ site.navbarItems }}, + + /** + * Add support / donation links on the navbar + * Example item: + * { + * name: string, + * currency: string, + * href: string, + * } + */ + supports: {{ site.supports }}, + + /** + * Footer content + * You can also set it to empty array if you don't want to use it + * + * Basic markdown is supported (bold, italic, and link) + * External link will be opened in new tab + * + * Template: + * - {{ year }} will be replaced with the current year + * - {{ repository }} will be replaced with the original repository link + * - {{ poweredBy }} will be replaced with "Powered by next-gdrive-index", linked to the repository + * - {{ author }} will be replaced with author from siteAuthor config above (If it's not set, it will be set to mbaharip) + * - {{ version }} will be replaced with the current version + * - {{ siteName }} will be replaced with the siteName config above + * - {{ handle }} will be replaced with the twitter handle from twitterHandle config above + * - {{ creator }} will be replaced with mbaharip if you want to credit me + */ + footer: {{ site.footer }}, + /** + * Add page load time on the footer + * If you don't want to use it, you can set it to false + */ + experimental_pageLoadTime: false, + }, +}; + +export default config; +`; +const environmentTemplate = `# Base64 Encoded Service Account JSON +GD_SERVICE_B64={{ serviceAccount }} +# Secret Key for Encryption +ENCRYPTION_KEY={{ key }} +# Index password, used when private mode is enabled +SITE_PASSWORD={{ password }} + +# [Optional] Only domain, without protocol (ex: mbaharip.com) +# Needed if you're not using Vercel +NEXT_PUBLIC_DOMAIN={{ domain }}`; + +const version1Schema = z.object({}); +const version2Schema = z.object({}); +const newVersion2Schema = z.object({}); +const environmentSchema = z.object({ + GD_SERVICE_B64: z.string(), + ENCRYPTION_KEY: z.string(), + SITE_PASSWORD: z.string().optional(), + NEXT_PUBLIC_DOMAIN: z.string().optional(), +}); + +export function parseVersion1Config(configuration: string) {} + +export function parseVersion2Config(configuration: string) {} + +export function parseEnvironmentConfig(configuration: string) {}