From b36b1964d4ee873e6f97412126f17be13f88c41c Mon Sep 17 00:00:00 2001
From: mbaharip <62494292+mbahArip@users.noreply.github.com>
Date: Mon, 11 Sep 2023 04:42:02 +0700
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feature:=20Revamp=20and=20full=20rebui?=
=?UTF-8?q?ld?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env-example | 7 -
.env.example | 5 +
.gitignore | 11 +-
.idea/.gitignore | 5 -
.idea/compiler.xml | 6 -
.idea/discord.xml | 7 -
.idea/drive-manager.iml | 12 -
.idea/inspectionProfiles/Project_Default.xml | 22 -
.idea/modules.xml | 8 -
.idea/prettier.xml | 7 -
.idea/vcs.xml | 6 -
.prettierrc.js | 2 +-
README.md | 93 +-
data/mockFiles.ts | 70 +
next.config.js | 36 +-
package.json | 53 +-
public/flaticon.svg | 40 +
public/images/404.png | Bin 0 -> 122875 bytes
public/images/password.png | Bin 0 -> 112997 bytes
.../(__components)/clientContextWrapper.tsx | 95 -
src/app/(__components)/compBreadcrumb.tsx | 117 -
src/app/(__components)/compFileLayout.tsx | 111 -
src/app/(__components)/compFooter.tsx | 32 -
src/app/(__components)/compGridLayout.tsx | 201 -
src/app/(__components)/compListLayout.tsx | 378 --
src/app/(__components)/compNavbar.tsx | 202 -
src/app/(__components)/compPassword.tsx | 96 -
src/app/(__components)/compReadmeRender.tsx | 43 -
src/app/(__components)/compSwitchLayout.tsx | 116 -
.../api/banner/[encryptedFileId]/route.ts | 82 -
.../(api)/api/files/[encryptedId]/route.ts | 223 -
src/app/(api)/api/files/route.ts | 148 -
src/app/(api)/api/help/encrypt/route.ts | 76 -
.../api/thumbnail/[encryptedFileId]/route.ts | 103 -
src/app/(api)/api/thumbnail/route.ts | 0
src/app/(api)/api/validate/[...path]/route.ts | 277 --
src/app/(api)/api/validate/route.ts | 89 -
src/app/(setup)/next-gdrive-index/page.tsx | 3 -
.../(setup)/next-gdrive-index/step-1/page.tsx | 5 -
src/app/[...path]/loading.tsx | 33 -
src/app/[...path]/page.tsx | 204 -
.../[encryptedId]/[fileName]/route.ts | 94 +
.../validatePath/[...path]/route.ts | 213 +
src/app/api/files/[encryptedId]/route.ts | 155 +
src/app/api/files/route.ts | 150 +
src/app/api/getData/route.ts | 190 +
src/app/api/getPassword/route.ts | 129 +
src/app/api/getReadme/route.ts | 79 +
src/app/api/password/add/route.ts | 69 +
src/app/api/password/route.ts | 47 +
.../search/redirect/[encryptedId]/route.ts | 76 +
src/app/api/search/route.ts | 65 +
src/app/download/[...path]/route.ts | 140 -
src/app/error.tsx | 133 -
src/app/layout.tsx | 63 -
src/app/loading.tsx | 38 -
src/app/not-found.tsx | 37 -
src/app/opengraph-image.tsx | 160 -
src/app/opengraph/[...path]/route.tsx | 228 -
src/app/page.tsx | 144 -
src/app/password/page.tsx | 67 -
src/app/robots.txt | 2 -
src/app/twitter-image.tsx | 160 -
src/components/Alert/index.tsx | 53 +
src/components/Breadcrumb/index.tsx | 165 +
src/components/Button/index.tsx | 121 +
src/components/ButtonGroup/index.tsx | 36 +
src/components/ButtonIcon/index.tsx | 58 +
src/components/ClickAway/index.tsx | 38 +
src/components/File/GridItem/index.tsx | 233 +
src/components/File/ListItem/index.tsx | 232 +
src/components/Layout/Explorer/Grid.tsx | 34 +
src/components/Layout/Explorer/List.tsx | 34 +
src/components/Layout/Explorer/index.tsx | 240 ++
src/components/Layout/Header/index.tsx | 259 ++
src/components/Layout/Loader/index.tsx | 188 +
src/components/Layout/Password/index.tsx | 88 +
src/components/Layout/Preview/index.tsx | 319 ++
src/components/Markdown/index.tsx | 113 +
src/components/Modal/index.tsx | 124 +
src/components/Preview/Audio.tsx | 31 +
src/components/Preview/Code.tsx | 55 +
src/components/Preview/Document.tsx | 59 +
src/components/Preview/Error.tsx | 24 +
src/components/Preview/Image.tsx | 82 +
src/components/Preview/Manga.tsx | 122 +
src/components/Preview/Rich.tsx | 52 +
src/components/Preview/Unknown.tsx | 12 +
src/components/Preview/Video.tsx | 36 +
src/components/Tooltip/index.tsx | 99 +
src/config/api.config.js | 74 -
src/config/gIndex.config.ts | 203 +
src/config/site.config.js | 82 -
src/middleware.ts | 6 +
src/pages/404.tsx | 45 +
src/pages/[...path]/index.tsx | 254 ++
src/pages/_app.tsx | 339 ++
src/pages/_document.tsx | 19 +
src/pages/index.tsx | 87 +
src/styles/globals.css | 275 +-
src/styles/highlight.css | 446 +-
src/styles/markdown.css | 151 +-
src/types/api/files.ts | 36 +-
src/types/api/index.ts | 16 -
src/types/api/path.ts | 7 -
src/types/api/response.ts | 53 +
src/types/constant.ts | 4 +
src/types/general/constant.ts | 31 -
src/types/general/index.ts | 8 -
src/utils/apiHelper/createErrorPayload.ts | 32 -
src/utils/apiHelper/gdrive.ts | 52 -
src/utils/apiHelper/getSearchParams.ts | 14 +-
src/utils/apiHelper/privateIndexProtection.ts | 45 +
src/utils/bytesFormat.ts | 8 +
src/utils/durationFormat.ts | 23 +
.../encryptionHelper/createEncryptionKey.ts | 26 -
.../generateRandomEncryptionKey.ts | 14 -
src/utils/encryptionHelper/hash.ts | 44 +
src/utils/encryptionHelper/longEncryption.ts | 52 -
src/utils/encryptionHelper/passwordHash.ts | 21 -
src/utils/encryptionHelper/shortEncryption.ts | 44 -
.../{generalHelper => }/extendedError.ts | 9 +-
src/utils/fileHelper/fileGroup.ts | 18 -
src/utils/fileHelper/formatDate.ts | 19 -
src/utils/fileHelper/formatDuration.ts | 23 -
src/utils/fileHelper/formatSize.ts | 28 -
src/utils/fileHelper/iconMap.ts | 60 -
src/utils/fileHelper/trigger.ts | 20 -
src/utils/fileHelper/typeMap.ts | 126 -
src/utils/filesHelper.ts | 10 +
src/utils/gdriveInstance.ts | 61 +
src/utils/generalHelper/axiosFetch.ts | 84 -
src/utils/generateDownloadLink.ts | 39 +
src/utils/hexToRGB.ts | 18 +
src/utils/passwordHelper.ts | 77 +
src/utils/previewHelper.ts | 110 +
src/utils/tokenHelper.ts | 26 +
tailwind.config.js | 98 +-
tsconfig.json | 44 +-
yarn.lock | 3765 +++++++----------
140 files changed, 7901 insertions(+), 7615 deletions(-)
delete mode 100644 .env-example
create mode 100644 .env.example
delete mode 100644 .idea/.gitignore
delete mode 100644 .idea/compiler.xml
delete mode 100644 .idea/discord.xml
delete mode 100644 .idea/drive-manager.iml
delete mode 100644 .idea/inspectionProfiles/Project_Default.xml
delete mode 100644 .idea/modules.xml
delete mode 100644 .idea/prettier.xml
delete mode 100644 .idea/vcs.xml
create mode 100644 data/mockFiles.ts
create mode 100644 public/flaticon.svg
create mode 100644 public/images/404.png
create mode 100644 public/images/password.png
delete mode 100644 src/app/(__components)/clientContextWrapper.tsx
delete mode 100644 src/app/(__components)/compBreadcrumb.tsx
delete mode 100644 src/app/(__components)/compFileLayout.tsx
delete mode 100644 src/app/(__components)/compFooter.tsx
delete mode 100644 src/app/(__components)/compGridLayout.tsx
delete mode 100644 src/app/(__components)/compListLayout.tsx
delete mode 100644 src/app/(__components)/compNavbar.tsx
delete mode 100644 src/app/(__components)/compPassword.tsx
delete mode 100644 src/app/(__components)/compReadmeRender.tsx
delete mode 100644 src/app/(__components)/compSwitchLayout.tsx
delete mode 100644 src/app/(api)/api/banner/[encryptedFileId]/route.ts
delete mode 100644 src/app/(api)/api/files/[encryptedId]/route.ts
delete mode 100644 src/app/(api)/api/files/route.ts
delete mode 100644 src/app/(api)/api/help/encrypt/route.ts
delete mode 100644 src/app/(api)/api/thumbnail/[encryptedFileId]/route.ts
delete mode 100644 src/app/(api)/api/thumbnail/route.ts
delete mode 100644 src/app/(api)/api/validate/[...path]/route.ts
delete mode 100644 src/app/(api)/api/validate/route.ts
delete mode 100644 src/app/(setup)/next-gdrive-index/page.tsx
delete mode 100644 src/app/(setup)/next-gdrive-index/step-1/page.tsx
delete mode 100644 src/app/[...path]/loading.tsx
delete mode 100644 src/app/[...path]/page.tsx
create mode 100644 src/app/api/download/[encryptedId]/[fileName]/route.ts
create mode 100644 src/app/api/experimental/validatePath/[...path]/route.ts
create mode 100644 src/app/api/files/[encryptedId]/route.ts
create mode 100644 src/app/api/files/route.ts
create mode 100644 src/app/api/getData/route.ts
create mode 100644 src/app/api/getPassword/route.ts
create mode 100644 src/app/api/getReadme/route.ts
create mode 100644 src/app/api/password/add/route.ts
create mode 100644 src/app/api/password/route.ts
create mode 100644 src/app/api/search/redirect/[encryptedId]/route.ts
create mode 100644 src/app/api/search/route.ts
delete mode 100644 src/app/download/[...path]/route.ts
delete mode 100644 src/app/error.tsx
delete mode 100644 src/app/layout.tsx
delete mode 100644 src/app/loading.tsx
delete mode 100644 src/app/not-found.tsx
delete mode 100644 src/app/opengraph-image.tsx
delete mode 100644 src/app/opengraph/[...path]/route.tsx
delete mode 100644 src/app/page.tsx
delete mode 100644 src/app/password/page.tsx
delete mode 100644 src/app/robots.txt
delete mode 100644 src/app/twitter-image.tsx
create mode 100644 src/components/Alert/index.tsx
create mode 100644 src/components/Breadcrumb/index.tsx
create mode 100644 src/components/Button/index.tsx
create mode 100644 src/components/ButtonGroup/index.tsx
create mode 100644 src/components/ButtonIcon/index.tsx
create mode 100644 src/components/ClickAway/index.tsx
create mode 100644 src/components/File/GridItem/index.tsx
create mode 100644 src/components/File/ListItem/index.tsx
create mode 100644 src/components/Layout/Explorer/Grid.tsx
create mode 100644 src/components/Layout/Explorer/List.tsx
create mode 100644 src/components/Layout/Explorer/index.tsx
create mode 100644 src/components/Layout/Header/index.tsx
create mode 100644 src/components/Layout/Loader/index.tsx
create mode 100644 src/components/Layout/Password/index.tsx
create mode 100644 src/components/Layout/Preview/index.tsx
create mode 100644 src/components/Markdown/index.tsx
create mode 100644 src/components/Modal/index.tsx
create mode 100644 src/components/Preview/Audio.tsx
create mode 100644 src/components/Preview/Code.tsx
create mode 100644 src/components/Preview/Document.tsx
create mode 100644 src/components/Preview/Error.tsx
create mode 100644 src/components/Preview/Image.tsx
create mode 100644 src/components/Preview/Manga.tsx
create mode 100644 src/components/Preview/Rich.tsx
create mode 100644 src/components/Preview/Unknown.tsx
create mode 100644 src/components/Preview/Video.tsx
create mode 100644 src/components/Tooltip/index.tsx
delete mode 100644 src/config/api.config.js
create mode 100644 src/config/gIndex.config.ts
delete mode 100644 src/config/site.config.js
create mode 100644 src/middleware.ts
create mode 100644 src/pages/404.tsx
create mode 100644 src/pages/[...path]/index.tsx
create mode 100644 src/pages/_app.tsx
create mode 100644 src/pages/_document.tsx
create mode 100644 src/pages/index.tsx
delete mode 100644 src/types/api/index.ts
delete mode 100644 src/types/api/path.ts
create mode 100644 src/types/api/response.ts
create mode 100644 src/types/constant.ts
delete mode 100644 src/types/general/constant.ts
delete mode 100644 src/types/general/index.ts
delete mode 100644 src/utils/apiHelper/createErrorPayload.ts
delete mode 100644 src/utils/apiHelper/gdrive.ts
create mode 100644 src/utils/apiHelper/privateIndexProtection.ts
create mode 100644 src/utils/bytesFormat.ts
create mode 100644 src/utils/durationFormat.ts
delete mode 100644 src/utils/encryptionHelper/createEncryptionKey.ts
delete mode 100644 src/utils/encryptionHelper/generateRandomEncryptionKey.ts
create mode 100644 src/utils/encryptionHelper/hash.ts
delete mode 100644 src/utils/encryptionHelper/longEncryption.ts
delete mode 100644 src/utils/encryptionHelper/passwordHash.ts
delete mode 100644 src/utils/encryptionHelper/shortEncryption.ts
rename src/utils/{generalHelper => }/extendedError.ts (62%)
delete mode 100644 src/utils/fileHelper/fileGroup.ts
delete mode 100644 src/utils/fileHelper/formatDate.ts
delete mode 100644 src/utils/fileHelper/formatDuration.ts
delete mode 100644 src/utils/fileHelper/formatSize.ts
delete mode 100644 src/utils/fileHelper/iconMap.ts
delete mode 100644 src/utils/fileHelper/trigger.ts
delete mode 100644 src/utils/fileHelper/typeMap.ts
create mode 100644 src/utils/filesHelper.ts
create mode 100644 src/utils/gdriveInstance.ts
delete mode 100644 src/utils/generalHelper/axiosFetch.ts
create mode 100644 src/utils/generateDownloadLink.ts
create mode 100644 src/utils/hexToRGB.ts
create mode 100644 src/utils/passwordHelper.ts
create mode 100644 src/utils/previewHelper.ts
create mode 100644 src/utils/tokenHelper.ts
diff --git a/.env-example b/.env-example
deleted file mode 100644
index 6678578..0000000
--- a/.env-example
+++ /dev/null
@@ -1,7 +0,0 @@
-# You can generate encryption key from setup page.
-NEXT_PUBLIC_ENCRYPTION_KEY=""
-# You can use any random string for JWT_KEY, it is recommended to use a strong key by combining characters, numbers and symbols.
-JWT_KEY=""
-# Domain name of your website. Used for fetching the og-image from public directory.
-# Example: https://drive.example.com
-NEXT_PUBLIC_DOMAIN=""
\ No newline at end of file
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..8dfdad5
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,5 @@
+GD_SERVICE_B64="Base64 Encoded value from Google Drive Service Account JSON file"
+
+NEXT_PUBLIC_ENCRYPTION_KEY="Rj3wRGB9nUElQ7OA"
+NEXT_PUBLIC_SITE_PASSWORD="mbaharip"
+NEXT_PUBLIC_VERCEL_URL="http://localhost:3000"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 2a4510c..4f49267 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,6 @@
/coverage
# next.js
-/.next/
/out/
# production
@@ -28,17 +27,15 @@ yarn-error.log*
.env
.env.local
-# vercel
-.vercel
-
# typescript
*.tsbuildinfo
next-env.d.ts
# personal docs
/docs
-/.next
-/.vscode
/src/pages/api/legacy/
-.vercel
+/**/*/_legacy/
+
+/.*/
+/src/_app
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index b58b603..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index 1a2fb33..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
+ Demo + · + Deploying Guide + · + FAQ +
## What is this? + `next-gdrive-index` is an indexer for Google Drive, it's a simple project that I made to index my files in Google Drive. It's aim to simplify the process of sharing files using Google Drive, and also implements some features that I think is useful for sharing files. This project are **HEAVILY INSPIRED** by [onedrive-vercel-index](https://github.com/spencerwooo/onedrive-vercel-index) by SpencerWooo. ### Why I made this? -I know there already some Google Drive indexer out there, but all of them are using **Cloudflare** worker. -I've tried to use it, but the speed isn't that great for me. -Currently I'm using SpencerWooo's onedrive index, and it's working perfectly for me from usage perspective and speed. -But, if I compare the price between these two services, Google Drive still cheaper than Onedrive. +when I compare the price between these two services, Google Drive still cheaper than Onedrive. For 100GB plan, Google Drive is IDR 269.000 / ~$18 USD annually, and Onedrive is IDR 319.000 / ~$21 USD annually. For free plan, Google Drive offering 15GB of storage, and Onedrive offering 5GB of storage. 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 -### Navigate through your Google Drive files and folders. -You can navigate through your Google Drive files and folders, just like you're browsing your local files. -You can also set the root folder, so you can share only specific folder in your Google Drive. -### Theme support. -Choose your side! +- **Private Index**, lock the whole index site with password. +- **Folder and file protection**, you can protect your folder and files with password. +- **Readme file**, you can add readme file for your folder, so you can add some description for your folder. +- **File preview**, you can preview some file types directly from the browser, without downloading it first. + - Image preview + - Video preview + - Audio preview + - Markdown preview + - PDF preview + - Document preview (docx, pptx, xlsx) + - Code preview + - Text preview + - Manga preview (cbz) +- **File search**, you can search for files and folders easily. +- **Direct download**, you can download files directly from index site without opening Google Drive. +- **Raw file link**, you can use the raw file link for hosting your web project assets or comments on forum. -### Site wide password protection. -You can lock the site so only those who know the password can access it. -NOTE: This only protect the site, not the files. The files are still accessible if you know the link. +## File Security -### Search for files and folders. -We implement native search for files and folders, so you can search for files and folders easily. +All files fetched from Google Drive **NEED TO BE SHARED** with `Anyone with the link can view` permission. +This is because Google Drive API can only access files that are shared with `Anyone with the link can view` permission. -### File preview for some file types. -You can preview some file types directly from the browser, without downloading it first. -Most files are Images, Videos, Audio, Markdown, and PDF. (I'm still adding more file types) +But, every files `id` and `webContentLink` are encrypted with `AES-256-CBC` using your own key, so no one can access your files without the key. +Except, if the files are larger than the `fileSizeLimit` (default: 4MB for vercel), then the download will be redirected to the raw file link. -### Direct download for files. -Download files directly from index site without opening Google Drive. +## Known Issue -### Raw file link for files. -You can use the raw file link for hosting your web project assets or comments on forum. +### File size limit -### Embed your readme file for folder. -You can embed your readme file for folder, so you can add some description for your folder. -Simply upload `.readme.md` on your folder, and it will be displayed on the folder page. +File size limit causing some files can't be previewed, and the download will be redirected to the raw file link. -### Override folder OpenGraph image. -You can override the folder OpenGraph image by uploading `.banner.(png/jpg/jpeg/webp/svg)` on your folder. +### Long Response Time -## Credits -- [onedrive-vercel-index](https://github.com/spencerwooo/onedrive-vercel-index) by SpencerWooo for the inspiration and also some file type helpers. +The flow of the project are: ---- +- Validate the path is valid or not. (Only applied for `/...[path]`) +- Fetch the password, readme, and files from Google Drive. +- Show to the user. -## 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 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. +So, if you have a lot of files in your Google Drive, it might take a long time to fetch all the files. +To improve the response time, I added a cache to the response, so the next time you access the same path, it will be faster. -## TODO -Todo list are moved to [here](./TODO.md). \ No newline at end of file +ATM, it roughly take around 600 - 2 seconds to fetch all the data on my Google Drive. + +### No support for Google Shared Drive + +I don't have Google Shared Drive, so I can't test it and implement it. + +### No support for Google Docs, Sheets, and Slides + +For now, I don't have any plan to implement this, because I think it's not necessary. +All Google Drive files like Docs, Sheets, and Slides are hidden from the list. diff --git a/data/mockFiles.ts b/data/mockFiles.ts new file mode 100644 index 0000000..6b435f9 --- /dev/null +++ b/data/mockFiles.ts @@ -0,0 +1,70 @@ +import { IGDriveFiles } from "types/api/files"; +import { APIFilesResponse } from "types/api/response"; + +const mockFiles: APIFilesResponse = { + timestamp: Date.now(), + responseTime: 0, + folders: [], + files: [], + nextPageToken: "nextpageToken", + readmeExists: true, + bannerExists: true, + passwordExists: false, +}; +const mockFilesProtected: APIFilesResponse = { + timestamp: Date.now(), + responseTime: 0, + folders: [], + files: [], + nextPageToken: "nextpageToken", + readmeExists: true, + bannerExists: true, + passwordExists: true, +}; + +for (let i = 0; i < 10; i++) { + const folder: IGDriveFiles = { + mimeType: "application/vnd.google-apps.folder", + encryptedId: `encryptedId${i + 1}`, + name: `folder${i + 1}`, + trashed: false, + modifiedTime: new Date().toISOString(), + }; + const file: IGDriveFiles = { + mimeType: "application/vnd.google-apps.file", + encryptedId: `encryptedId${i + 1}`, + name: `file${i + 1}`, + trashed: false, + modifiedTime: new Date().toISOString(), + fileExtension: "txt", + encryptedWebContentLink: "encryptedWebContentLink", + size: Math.floor(Math.random() * 1000000000), + thumbnailLink: "/og.png", + imageMediaMetadata: + Math.random() > 0.5 + ? { + width: 1920, + height: 1080, + rotation: 0, + } + : null, + videoMediaMetadata: + Math.random() > 0.5 + ? { + width: 1920, + height: 1080, + durationMillis: 100000, + } + : null, + }; + + mockFiles.folders.push(folder); + mockFiles.files.push(file); + + folder.name = `folder${i + 1}protected`; + file.name = `file${i + 1}protected`; + mockFilesProtected.folders.push(folder); + mockFilesProtected.files.push(file); +} + +export { mockFiles, mockFilesProtected }; diff --git a/next.config.js b/next.config.js index a843cbe..8851780 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,34 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, -} -module.exports = nextConfig +const withMDX = require("@next/mdx")({ + extension: /\.mdx?$/, + options: { + // If you use remark-gfm, you'll need to use next.config.mjs + // as the package is ESM only + // https://github.com/remarkjs/remark-gfm#install + remarkPlugins: [], + rehypePlugins: [], + // If you use `MDXProvider`, uncomment the following line. + providerImportSource: "@mdx-js/react", + }, +}); + +const nextConfig = { + pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"], + reactStrictMode: true, + async headers() { + return [ + { + source: "/:path*", + headers: [ + { + key: "Cache-Control", + value: "max-age=300, s-maxage=300, stale-while-revalidate, public", + }, + ], + }, + ]; + }, +}; + +module.exports = withMDX(nextConfig); diff --git a/package.json b/package.json index 4c98133..5e77880 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev -p 5000", + "dev": "next dev -p 3000", "build": "next build", "start": "next start", "lint": "next lint", @@ -11,57 +11,54 @@ "pretty": "prettier --write ." }, "dependencies": { - "@react-three/drei": "^9.66.1", - "@react-three/fiber": "^8.13.0", - "@types/react": "18.0.35", - "@types/react-dom": "18.0.11", - "@upstash/redis": "^1.20.4", - "@vercel/og": "^0.5.4", - "autoprefixer": "10.4.14", + "@cyntler/react-doc-viewer": "^1.13.0", + "@iconify/react": "^4.1.1", + "@mdx-js/loader": "^2.3.0", + "@mdx-js/react": "^2.3.0", + "@next/mdx": "^13.4.19", + "@popperjs/core": "^2.11.8", "axios": "^1.3.5", - "cors": "^2.8.5", "googleapis": "^118.0.0", "jsonwebtoken": "^9.0.0", - "mime-types": "^2.1.35", - "next": "^13.4.4", - "next-seo": "^6.0.0", - "nextjs-progressbar": "^0.0.16", - "postcss": "8.4.22", + "jszip": "^3.10.1", + "next": "^13.4.13", + "next-seo": "^6.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-h5-audio-player": "^3.8.6", - "react-icons": "4.8.1-snapshot.2", - "react-loading": "^2.0.3", + "react-is": "^18.2.0", "react-markdown": "^8.0.7", - "react-player": "^2.12.0", - "react-toastify": "^9.1.2", - "rehype-katex": "^6.0.2", - "rehype-prism-plus": "^1.5.1", - "rehype-raw": "^6.1.1", - "rehype-sanitize": "^5.0.1", - "remark-breaks": "^3.0.2", + "react-player": "^2.13.0", + "react-popper": "^2.3.0", + "react-toastify": "^9.1.3", + "rehype-katex": "^6.0.3", + "rehype-prism-plus": "^1.6.3", + "rehype-raw": "6.1.1", "remark-gfm": "^3.0.1", "remark-math": "^5.1.1", "remark-slug": "^7.0.1", - "remark-toc": "^8.0.1", - "swr": "^2.1.3", - "tailwindcss": "3.3.1", - "three": "^0.152.0" + "remark-toc": "^8.0.1" }, "devDependencies": { + "@faker-js/faker": "^8.0.2", "@tailwindcss/typography": "^0.5.9", "@trivago/prettier-plugin-sort-imports": "^4.1.1", "@types/cors": "^2.8.13", "@types/jsonwebtoken": "^9.0.1", "@types/mime-types": "^2.1.1", "@types/node": "18.15.11", + "@types/react": "18.0.35", + "@types/react-dom": "18.0.11", "@types/three": "^0.150.2", - "cypress": "^12.10.0", + "autoprefixer": "10.4.14", "encoding": "^0.1.13", "eslint": "8.38.0", "eslint-config-next": "^13.4.3", + "postcss": "8.4.22", "prettier": "^2.8.7", "prettier-plugin-tailwindcss": "^0.2.7", + "tailwind-merge": "^1.14.0", + "tailwindcss": "3.3.1", "typescript": "5.0.4" } } diff --git a/public/flaticon.svg b/public/flaticon.svg new file mode 100644 index 0000000..aed7d74 --- /dev/null +++ b/public/flaticon.svg @@ -0,0 +1,40 @@ + + + diff --git a/public/images/404.png b/public/images/404.png new file mode 100644 index 0000000000000000000000000000000000000000..0cf89fa8aceadc2cf0ecb7cce3424a3f4f2568bd GIT binary patch literal 122875 zcmdQ}Wm8vE`S~y;Ge)kfGtfDZOlfna_kcXms2Av{Wtd*S!^%Wi-IPr9(;`lK(Dd`M@yoBl-GuMt3*QHI|yb*AQ2gd$^3zwfDTrGctSvf!y2 z#W}-*B6g^M%9xuhR2i1VV}&QP)v0g!HSoEOQbwp3W`HeSV~skV4Jjsj>oG9)#1hjj zC_T}$xHxb&Mw)f&|18FfCtaSX?jv^<5OnG^r%P7`<0*%HIOG;+M=YLZmn%-GGW@Ut zmGBu#z)t{2hWCn?g^bg_KAN9Cp;6IynTq&z-*cJY{M<7SoaDlv4n&DC5M|UdqTi`9 z6K=eb5<8qZE%v|A&cF0lue_i^aJXY#&@V@>jI3Lc=m^ANNc$<^8c&>@+)Sz76W9St zjaq|$#b vhSDI9iMdz1hu`7XDzK!7M=T|G105Zg)?rwiE#Evt8X@FRqmfT9E z8)b>~3d{@1trXCNHxo3s+4Et}E)(EwcFeBPonT(cCM~2^kh^Jmo*#D(z6`90eA^(> z5?I8oyFxz~xyE0@uvSk?zlt+f!u*9)a3CFD2GB4oL?i5DjfIsl86~&%dq<-FNPvb9 zjovoSq7(}K-4CmuX;Iw!GLuALH8;XuKC8j7JFXF^V&5u9M8Ls~0;eGlP%WQ8TK6wR zW1(mBk&fXbCZu0CRE9FlNv7QRv89OevOSMEtds}f8REtiBS@21Zl$DZSXdGqTyiQa z77hdEm`VzvfA-&g$ZZfzo*z{qWnt#)FO%c?6FXwS^`^4QSjUTRFh$|p(Pi2Jiibwz zShOc{*+Oyo9^14!91F+P+a~YZ8(SqsieYwVputJ;wO(;x>!ioQ3;n3Uq~Xcn(QhTJ zPcpXr?0Wa}8+ni^wS&thJ%{CbPC^7;-jhN~bZR21>F_fN*mFl%i_^+VN3*k3eVpdP zCv!nfnx9mw^MB$=bm^k%LDAW)l-hl>Ac79gH$!;K@5Zcfz;b!0f~rz)j4Z~0ECLOp zE<8%7BdtXFxj*uRQv%({IHDDigrpPeK|9G6SxZ>CfwKwBdMIq(Uj*$_B++QS8;Q|i zEi(drqo^S3>m`&N@5hyI)|wo#5i`yRLq-OBgB$)rzB%Qxl(t&?bPIWnsdCz~k~U!0 zNKJZJ?JjS54PE)L1$S)362=c6Qe|(=6Lgvw?BCNO<2&MhR7Q@P<^&CLf64YfK;PMT z{3Nri7%qRGv9SEm+zP!AN^wdoW8Vi<=<5sGJ7XzOrb?q1Z4)YWp_EL2gP`nDYN@%G z$+lzB37w*yFXsh@(~R=IaWf7|^}(#kI^2B2wo0>T$LMI TE{ty zHidSVO;{R5ad0T)Tj0~Ih_4+15DRaF0L6*s=f})6LEWlbiDu19Qng{k^gcQ$7IaQQ z%DD^+{e3BBl13D%J29MKIr|`(cc>=_isDt+j1GPe<<$K329Ue>HwwuXn+Culn@%J= z2+5?6DVBmNDeEY-$>ki>3az?uE@GcmeIC iNUi%qas7h8B zv`7=9xA-Y4TUcIKk)r3Wn0k~rm`#)}+UNJQbGM+tY?Xa-phYx+LWcE^g;=6yxxSjB z?YDB$X+qI{oSWTXy;j%#(;YY2&W{fQib8i-UtGTwqBo`UNxyAW8;+Z!e1#dgft_04 z+4JS%PWAu&p&&~ery4mu{w0uFC~MG+m~78 eTWE5tzE-Ai+`MqW3l)S*ORsK_=CfB;>S+KoE zGHEJHq|q;yEL7a9uJ}fOZ8iJhyZcjMuXMN2xt_Ml$bkxaXJyb7cYlC+UIvVmRu F@ YExbro4uVvDxdsvAuv(*1Q$Xja==9CgHArP3>=W(&t9Xx=`J6Q$}(; z=_s$7nV=YUxqi*z{WmG`2+K`6>Tgvm6Ga_$`e8K>pVimz4FO4xegpCl$ttiGo0=rA zi;c7$akL6#L^k(z#NEPXmLpk~a^1vVYp;r-BCi_LP0^dUGR6BM#l%s!#$VU|&2owR zvj$wyOOq88g}3?j0y)x>zL3MmQ)w}FN8mSOd3}X@mEVXWgh8r^+0H`HDY*z|3g)EM zc-B;7mi5g{6}uqkJx2 _uY56>w3VB+;M0`_9`o*OfMaSlo!35kzN^WE10Ndn;P zbiC~*(W!v?9GVf(nnM~*@(AK_Zh{(#;xkLk`6e94?z%;Wd>Sj)mFDAAlT?u?ckU3| zwOj_>C~S~CU)BRFRl|M_b4Qo%{ULitGg*o|bGSzSFOVkpXaoBe;(RT)v-VGx<+7 $o#cSJa#QVVT4SS)j1dOkf5 z|5w1<=OWA(_qlm6adD6E z=#`DR=`kEmJPZ5Eo)M5 z* _2j zh{Jj~Lhn(Gfhj=3A}t%Ro#06a43?_pQlcfP^InE@A3Ts-{5Ib+#V+$HlE30~QOa)K z8okK+6H7{3AfGMWTuFDJYL!{L?qsXKs%^?X@rQCLe!w@fL8rt*ZcFw}4zH67UhUD< zitTcIta*l#vvO@rcj7S!z&%q@jKiKWmGwcI$P;B`^f?UyS$Ax-!3jmRR3yHm(c6?b zr?%I>Rr_0bd%5gB_(1xusEaiXseH{o#evMZO$?OnJfL45{%0p+GQLoO`I}tW)S&1d z PW*{6nwCg*jNBm)0yz0CVK|j4UKEa7fl_d<-?d zMT!_NhT`Y`JJ?YCEzD< -?f7}AGp-^WK*mxyU$$p4+SAz^) FLy23f`x`OU!t8;n9zhT0rtw+PAL=+n?%2C=O7Mbme z22D?%L4N`t4A!w$9s(goSvgx;gJNSz2(~w6+H(jG-ZZ*#x>Ge~P%I$s5`)rQ@oz(Y zwTws#&i9MAwjrLZ&F^eBP&_4+IqRpQ0kk`bGXgQ;>;aagG&a>}ka2wFp9xwkR@K|U z9H%$hQ5>k>NjZQmhbc$B{hRp4nEuYEj9+k2GBdh8z5Yaeu1V58Z_{nnCTOt2=4IN_ z(4{L6Gq?F_(rP7UpemX1zEOoWhSg-Qor&PIn;lD<-y4&wSlP*Tr#yjH0UY;Lsq_G7 zN-25G?v?R6Ce4ohMtj`! |A_h2KA*esGM=lG`8twfuD?(Q 5%*21ChwV@d=+_FLX|2x;Pj0JLKtVOA9v!$rw#w^?2_>Cr3+LOMg$ zRBm>PALC_zSESThDWo!#BwW-g$xcro>_z*sqyP0Kxcy=o@yEcu)6LdZn3>O`&@_0< zsa~W~cJc2=liehc!G7*~9GmO}{lzIr)`K`w)_!pGZ2>Lwpc3yW>>B#LfGb|mmcT_G zT$( 1uqrLt!=g%~p%AJI(;zXOh%mJG#-*YBbfBX)`@u$u*2&SHA@Q3} z6AwVP#EQH$z{_KZzi6~5vwE&I pk(^Jbdcua6#q}x34=zaCCbT z?W`4U1`?aEqM3B~Hg=6$Uv#2WT1*Hu+S*EW-Y~I#=aUAL5`1Thb_H9PGIqp_{*#i) zH*?Nxw%YKoa~1z6c;L%U+eqy2CaUVSS$s|B2N_dA<)ZJ~ &AgJD9gN5egk`%T&M6ktdYuQ3QgU`g<}~QkNQi|0JB=i(6v2Q zmSmuBR^~64=6d!)0Pz8*%12Pu@6m4bs8N3{FL6eFUBM8PMXz@Zr|NBY=_aNr!kOSz ztl*56c+JB%+=GvoK*JV;)3BU JiZ?m5G3rDb3_Mg^>e zY=(ymb6(F$+A1~{Tv26SI`gkLt5a-URGhECLL%Y5+NqVh$loM4TY_(GoTpM9pkZyI zqxV(^HY5IHqef$=4Ttz^*;KkE!PlZ$M`odUinJdnp&L5fbn+GW@jSRfyt#5i<<2Iy zCRR%PZZ;!2PXCw^j~AvImL9%hJe{m#F1{87?L&g^Oi`YS_c%D-X 3NzH<;pWEbEM4v<%n;Fp?jUPbR{MM-L)M# z|2GR@MW@OzJVz=}I$ZU`$)&N4&u7N^+vXX+3&Y4j16+|uvy+miVl19Cg$`{n);3#Z z1SvTsXcjB?fHvrqRsWdmSQLHpq!sEZ!J&KF;%{VCn 6P4J07bOrbV|PgqWde z0+<>>r9J4m5G3N8n;Qb88;a>zmtJWqr$&}Mo>FHaN@ahUGeWC&74F5LgNuwl!RUrP zWs#RWn1yR>cP}$zGHKB?zEW3Zp)X0{Dl)L>95F5x>juk9<2|AQ143qksyMW;l iE7SC45HPp14b8gt;9EF4dvQe1zDugA(on%gq*uB4$(=3 z$jBgOb&3s~^CIIl24Jb@ad6ccO+q23l{5Bc`tJ|SKY~s8cX%WNGP7b}zBEbvgN4@N zR;+Q`N^ZR |LJQ(>9&9t5sl zoi$EcRpbN?xl$xCbQQcn3G*#djEI>r9D3v^?G2it*MTps46HL~*wu)jcrq2tNSfU6?c}-?kCHv2ssRMs5>~5u>uW+#*aO znnrkRXRWk(kJ6%Kx?m}eBdjrK1`chTK%6mdwX!)gax7{xQ>nzF?BUc&aEm&|vO&@= z!#T>NVTg<|tJm9>=b#f4ATR+-lUQjb-vBW&mc{{123R+6NyVu!LS4jh#&{es7`W=a zCcQ3WjT#G~SYnLG D(6=yBWd@a5E7$J48vly zgQ_7L?_eEayezd6K3jM%+X76+0h399AwnA>2LC^6Z`NyDdR>Q&`88|peQr~?I`*VU zNg*v!QcPJ1tUz!Q#{ra=K!BZxJO&8@ xY>_r5jks;cJwmse{sr{a4v(aJ+vl=X$a~eB? zl0C;w6<5cK>|#?J{I^CbacSK)g>sXqTh7!^Q2qBQJpxErnp(G^h_*B0sedMHcKhk?e}md8N0y-ayfNSo;jCvGC{+-TOy)- zLjR{{7y0!kWQ8AlJt={lpI?;Tge)Mn%J*Q5fl?ZBcK;aO+~&FmG`QVF2mwvW+Ohf< z%K|JRqi+>md4svm6{v-k>DgIE2V3c~m7zOuA&>h9g&0sB%7EGcN>O^{L9m_TWh3;m z6>|Aw2e!Hmf!1ag56PgH);WU-W<%lCfWt4P*p83V0W_PXu&QC37_r-viPUR@NI_E$ zD8tsKH<48$F$VOjf@QZl^opxPfnIEgnZxDYHmFXTWk?Z+t>Nmy6=Gme+l=9|TNfnw zXHHy1WegyNi_B;%;@Szo1H$w{f=p9A9|XGQz-?z7MiZOA2!LkLM_vdU)LK!?oV_@& zKLo*UPj*>3b}Tm68DdVGn j!_v?LxMr% z07!)`1`Wm-Y)&NuZUU$}7U?){6;k-UlCaoNl{p-?E~j^PZ?&mGcC1$SyOqV#k+jf5 z?a +ZQL@PC^(EmRpW6&0tgv?x8d~iR5+_97K~JZP+j-Bca1u>h9Po zp$)J806L(0VD|w!1cto})w3~TXi)4y4n}otts@|y)Hd*XfE^R*cuYzm2IQ2mZDq_O zoF2-<4jV%9^mvDLikqu?a1|V`wlTkODy}n-oE{9}Fs_3XS=WrXfu3`@eSiv3`Rs=# z {7=Rd*b8{q!h5Od6?FDP;0#n?6zwkZlHCj`kkSqV^-m_n$vyn^F0X{ z(YuQ9Whu?XVZp}*^PZ0G%)XOQZvvr3OEovsoYU|Na#orc^MnSCT+kH^9`R(#_TK`g zKitX+(rKyg&E!FWt_-}7$sTS}i06G2!Ki$_TTwgzr{oU~$(xPCbXx2f6yr1xip+c+ za hSn?>l0Bq9HE*U|XQYrorB{2|Sp%(;$xcOQ&1?w=z#a$>K~Jb(7W7yj#i{*M7% z;3K~N3M-TNG$0EvvJOnqFJNz-#tfF(VHIe$R?Mpshf};-lrY_2-Tx_C<`okTI_^nB zy@1#~Fec 2r3E%RDP_gXYg?vbcoc{L}#LLfH9J`fIqK6M*xZ`Aa! FF@r>6a4CvD#L5J9D zCG?vU$sPfM0@pargzpSa&R1P8;@UUPahALvlzFnl8hUTIcyuum9Y6%58=* Nlc|;N5#CWaV&=9YQD{{!%)2JI*P#t)d<)+;b=dB_se# zj8-`nQy%+4?_$HPI2l3$LCzWOS)fhry4eG5LojE9Xy@=Wd#5F= ZgL^Q2t&^y#r)ndkrUj7eg z$*TX&RscA=sFYbSxemjhmt;zD?VQt)G*QvVSkYay%~uVuBFWE)+E+JQmtgJy&FoGB zi6GHX4Jd7iy16LO^+?9&;@P9@fJjKNWExOw8NYW>i<|9d8S_MA09wJfnv;gxws>K6 zdd=C9sHx5c-l4?EZdcWHNKBZ;2I!~j5ghSwmg<0)RpF+#r8I1%m_X5tGA%B+!DBFH z-C*mTGz191ZjDC(-a37x*Ao*+>$o0%?9h=j?kaO$u+hF*#&(8>51!m3ODvmvJ0FaN z;B-n`$^(d?p+htvMQhtaA2*O9BDYx(NQ}^0Q40scWou(#G%LDbw7EwOC5vS-D+hzjx*c!|`ULAri }Pz_E2KD_M2y zv<#DOPP;y(cALFRC0ytXIly2)RqF&W&Ux=08hog$KytTZ^Ds&Q`~8CBkr+AO*Mg4y zp6ys6!p``3^o}eJD9SrPFUVj}ia-3UK?wJ3CQMc3FuG`?%d&D$io)$(TvHOp`G5eu zyCX9(_Sza!2#~Hwwgsme&R 8Y<#utX{m3@@=st&Z5Yl`4QNmMj{HM6CRXjpS!?g&vYO#o(J@?I1(N^Q~uma zFsM&-JkRVV8qitL_1ewN!_6+en4Qllp)4l)pa1mr7XUo?x0XPl_;ML=fb;a3MT^sT z)2s}AE>fGZw3dVz1!d#6^UO?!s@pCr@|w_(&CCYp0r5(Z%YJ~S#3WnkKmdh0wLw67 z;I U1t~Tl~a>ntnjS3FhvF?`P znk*KqqIF_(PGVkHo=Cy(wp-H>iK+qt3UQhtfF+qmpryggf|4LXT}>n;WU$4O0@{)Y zKlp6S&HB1o(OCDCYzR*gX&8Ekp-0$}5Fiqe6X~o=j@T^;Es;I7E(UQaD^vx{cEm-F zKniA_88CZ$gFUX92U<3&cWERNl&ucGDQz7^6q;EXt~ny7fGctPZnHwKWWW@oEBoBb z-KKq~id#&Sjc1!PE8yCG8Sc>`kb$%2IEn`Jj+_G2JG2nU9J906ExH_QK{XcEewS@m z3lqU=Leg40I_e0T!7N2eDQFb~-g4bd+1el$ +j>tsji%#@p+zI zyf0vb&=*URAg#&Kr+{IHYhkgodZ&;YK+{>WX(lihel&h~3CPa#2Vik!rF XULYU^mNA_c+Ds$uONVz$ BGC6maAE4y{yr6@k=LpEtPOM&4x$)@sZPCg+%@=U5R2uaLaORzM T#E%V9@;QEFBd3M1X*nuOhUkt$xs6n0g9@I;X;ukXAxekhz0t%EGVVoc(nnD zNI^zNks=4_tn{th6(&o_#NjF4vWm-s*_d;9MQhb^HrSJklUM 2lw$>K(7Yr;%n3_&3l4*mMNpF>24Hn^3K)=c5H#&bA>eGkH=(BB zY?n`F0fq|LYynCL+F>ezb&tZJ33P*_SolHNT?LrNsJdgUVrPnL7a|8`;R*%zYaBaa zQWB^oJv|}&EGa>DnGCJk5WynIg|fXfCPjk V!}@`&}3CyoE!k*HQcWYQu3W7 zOc*I8Tszxg+pZX&8h>w20TI)+c!}ZepS^tNwZAGS{IwFu&wT2se{}o7@xOz1X|-X= zvn^va6d$u9=n{a!>Ow3StF4+mGli*JWbhz$XyX^nDPXtXn~+D*v=aVGe)A%$^v;%i zJfy0qP^?SDQ#beXv$?6_E)#>y5sOhu0ajfARo3M=_m8yNkV8ORBD!@SDW^eSWk(Sa zz@W~`n=h+l=kKviZ>&ODC$&nrATJqVm$`D*$N*8TYOG<1qk2D4janvZm9)nYGu-|< z#Q^QW2Glz26Gp|W3N#9ja!SOHYe$F*G#+s6eP}1dFa@U>tFdm@{Q?_1qxXXNV~)J4 zVg&*Ry^h(sCATYgdvj&Bvw8f1CLv)v>KM45x{$Wo5tal|Zs1#NWv5fPA?!?>K^y*n z`>uxGC@`fiCfiJfx)J&U@w~a~un4f*Edw%(F=DIRgft2@Hv6-6vaWyu=PK0Z0i?iA zGsA$|DqM{m(liu>2fCC7j2P9 ? z662U*dS1#YB8tzccwU0tS3mE*K+8MW)46IC3_BynDeD+;qA?S}vR_R^sYn52W}5E8 z_jvfAJP7AF!G)M7vk*g!&?=J=VY80ZWcbL%B$(TsjkQ(PG$;YRRqS`kX2pWtF2kwu z&ge?lR%mM|+s5P-6I!ik_A^0vruI3GwZyVchh!Cb_HX~f^Irk*kDinhe*E>M1d?yw z$oKADmaZL A6 zK!S1Q3p)oJB4)Fm@ZAXhd&aPc_W-@OF^7pZPZPaZ{ah~U&DS)00f82jjMt8%d{_P zjeA>isT{f?F>2)1j5F1eFA=5{iEc6iN68ldIBnQs~!tv4O(YMnem zi}RBTT)<9D?YyYbynTo*Kr_8dx1y+z%2o#S<>#zb#b!&FT5C*-W+DmD>fLoD+ksX& zr#QRQl%pxS{n_-h`@`Fh@PoG=;?9EuKK=X|c8OY>M~8-={={`WcXL5buB}icOm8q} z`+0ZT;E){e>8$1z0YKRZXLVI*>4+TN7(-9&Y}k#kNX?C$EHJ7S5}aMw{W>WCeGsp$ zBW1Co!ORtf&}s 2!I^8EQVnpD5iNRQob1#V(%wAxT{?7lM*9>2dHRf#HJ7pQ9 zoRnaSa*X17Y_IG d$LmQ6 z 40s~(2RHHLuN+o%SjiHqhEEgROxNsIP%zf9)NObe!A;d{H5 zqMC?|7_{Q6FW$zT^CRw_Z}`#e3kt8I;H}#iSaZY+uinAm_^GGynU7t^7e9RyyA&80 z60bqGWE7(<;puj`&8d~appNd$uU*+HhnR+Q2N7rhhUm$!kIhKNu5AT;w}U4pg$_#^ zcNqp)H7_aP?t=|ihS6`O @D2bQLR0m4Lnv8pxMfSf$w*A%+SfC97Wz3 zP?HiLL{NJ-I(9H-lEsNrhhw`ROt6V=2`7O~f&gv00BlE!Z 7<+Tb@)$E^PF~afjFsof}amibU?I@=+t{((f z6RQpVAfO?c&JHP<0MHYN3v_%z(7G((HaH%j5W*Oy`-7gBGzP!Db%fzbE#n_vp>RlO z^D3cpFGWDKW CpfS09z7PKGObBgiYU}vg_ddYC{`OnQ zIb+EY4=y&`eYhcriz@loViY@LfZ`q-R8>54bH%5hJ;NuSy@t;|e}-Gv7F^pAFK+hz zJV3Df;gB*d^lTHWervU07X|TzxIx$m*3C>27_~OlHY0Y3cL~a2F%zhfF$|t@OK^vb zY9%A U_WKx8?0yobR zaF+1i-Amj%KjQUw&+(uAd!NRQv&EiI9hNe^E6_2Zw;*m~ UscwQukp#X}*AuuNX)vVc9M46$d_-af+_b z&0G+8xH^s) |EM>(h?$*Bfkf mYSg7fV7&Vml#H?B<) z_nE&xnmJsU?;!ws11(KjNAEJ6Sp9P;bigbr&_;Q3%Z& THL#Jeb0?5WxoJ!*|MQu&qHw@TWhxga7rnU&m)ZzT&BC zgr0_hAD>y$BrUm~*|6}>UcQ3|7Ztz$E1$&mWt#B0t+Wg2#PNFwAd{vnHjh%7Nt3%V zjKXUcA)LXeIS1RG5c5xS%5*l*A0OnmN@(cn*zo @o)b81HAC+eWWP3aV=T!HfxbE|E_`o?67rw z^Obu@G2rk0@~3g*EWv5=+>cUbMGg`MV34L$v|;lzz(jw>)bh{HB-3XC=qK(G!IN+w z^xtwqeFaY9MWi*%w$)jnbx-b!roggGgUzW1@0}|2UDHI@X_84w@nX=5HWNW7F&Z(f zyRlhNUZ}l-A*tOU+Sc2E6_1AvIp^_y4*R-w5!VkW*?aAXy0aaC%0*HkaU0@~j}c-J zkg#b^5hB-~dww~oXg}KPNeLtzOMhAvCK8)a0|jtAbPmG+YS>cSU)}%!AOJ~3K~#T7 zOlGM8+c|9q!~J2j)5h9i8sC*MPM6)ZV7dgYvs3Y*vk#>m w6g7^%trmY4+|m%4t+Rq~^)!M4SxZ z4}oV^1qaF=L?wwW9AbA;j;?Q@{>2Zp?>)HM8ty(g;5$FOk3anL+xY2E@8Kqvs&?7N zbx<^w5g?Qi7BLXox_^Ge{?-l>`{)8Zc{`8Ybsempe3t|7W%Pw$0`7sfgUhaR!hQTb zrjb$K_aSpNu?^^(0*|f=-o1B)_wOI@o!9Q+?)edGj(G0Yg3ax2k{KTYaD}wPQU3W{ zXW}_ yvAvCg>hK9vxtsO2boU^9U1&c3dQ+)M% zxAD!F@8EDOc<$CR;4XjOJZS(V$+XUHBn05bS;DLDJj63M7W~Q=pTo6Xf y= zp4$)IAvonoqe){?IW(G58XF9ow( p@!dNRithZI07Eura`z!}IX;`hYYlLjWA`mg@( z=l 9?%mnCC})mdof&m 7=+Kb-;FS*sY0 zMdAn3VJ8T?wr&T -X`N5doGI35^K#63w85 z`EZ)RS^!6~qJ0iN$QbJEA&i6~O2F9~@&5fQeD{s}xOtZGndhz{+d`mj(5TzP_ AcNu#DUH z5BSrU@8GL1en8SlJXvgH|LkfenS=wzIY=R}J27I7h~U|$Ry_CA4!vW*mku9$@?&>u zkgiwlCaCjd6$0OrT^L4E^iG*N4F0??VC!9n6L#+%61}6rD4Iqrb4K7Oh;7}2&;8nD z3c9f+(F0rB7C4OdxFZ(O$IK~M+-y1u#%N5a7|qGs?x#QZ&LCp}J{Ns;hs#df0UXW? zL2&)X4J^xwX4?=sQiwz@>Jh$Z&l3`DUUXc;tF2xA!+-Y1KfzyPFZz=@khd>d`+N)% z6vSbj9irOD&GRV}D_aJOQYxgGm4_ F4PI* zus|@XpW|^uUJ{mt`#t!5>ZrPXi0QT-06Pv 9V)=Unj-Kozx6JD?( GW9$?Usl?{O*VN{cpd6t#mUp^zT2dZvdDJEKyTbzz99V zCBhT*i*G)_7d~+vKl`bhr2VQ}Zum285N2l~C02MsTu9f9{>$r^E!ekK5fBiPCl3;6 zD54c(Vzu#k6*xfx?0cNho|~D&x*r&z(57~O9D)tj(HT>-5&f7f!#2ADv$~%J(X?g) zfV`%`OdA4eJ7=!QA6=|WmkLKy)^#25D6C*ew*P#*jm~brnezYgaLMP(6Hn<@j5-}T zpw_ bSw#8C|BYlrKMrFrJiycYrwsnw?0k+cd zN8kMb|KRU@0hhL6&nq7>zbejsNd5I7;r>b?U|i!6;t*Lmqa{S&;z$iZFfu{!9ZODV z3jE2-ckqAwtJiVs#)2qX1lP3FF8 zKD~}@`h%* {bg$i@hE~hmPH%78p>5Xhix3gE6 z;iK(BFSgVvSk?TUH}2u~g9Fwa#%#l1{N5%i6d+3A`4R32a)(@;{Pz?oNW6cs;rw#L zVr~bF8K<)+rFP7g$teJ7GOls6XN F&5;tj-ipa+ zB0JF3?KXP|F$~QP#A6K4i@Pcd6w)~OpDwvPFT?S-J9~=rra8Nu9i2bhE`Ihn=Nb@+ zQ->3xL+7)r@nTlh8Ex#+Vu%p8iC5QrOs1v^3mrFPY`r7raQNO2KfHi(YW~86@E5$E zC>r_S{`+5k_^mh2e S_f0n&x1H?2@H%@82K~ffwxx0^SAbR{;Ay%UHbX+4l|#q{RdJJ z=3aQ85r#;*yck4Q%iNRUkn$)f939!p^ba#r#BM_nPYNzhb*%)y$WNjhYS30~gy6k1 z^I-3D+G;DNoeAn^)#K&pK-3}mlVY&xI0NHaLdJDK*AW~2-+8EW@{a#`URI3uTNQ`n zC2HO5xxu) s6RazvyUi9(t<1+3 zGL_-HsWAJ6fjiFw<)6_9!r7P|y^-Bjt*zA%@WPw-araTd*-C}G54Hv1eSlX%fOM`P z^~$ZOI&p(Z6P~MiPaa+r?AL@=xa+w#bF+;BV#%E=MGa#eFqE;X`rN+g0@7izZhzjJ za;0$$c>TRc_@Dpp@8PK%T*HcRQu%^Oq4k;khH0L66QGIaSOqIi+vWEQ M)x#uE_V&xLP*BFBKOb$som5Am~~c?v)MiR-|a57~^z6#Fhh?9F_hM6m8xl&uV3 zuC03)Yv;Ksw!>zDgEvGwKhP aa2T5x-%Y{6YKnq4-#g&{`8RLE z?aZwh<=Ksb+7-R=T6GIJ0B1&GHL9N6i)i dnbS9tSK+%d{d_XOf^d*ib@H~ z#hBq1K}10kQd$7L99vnP3hZ4;UTBkYS#j&H$O%93dh!pK_8hv7s5x5l*3pD8gm)xR zI|!IJydEoCB}@hvX?fMKObY|nzWb0(#Ob8Q(2-dMQavt+nGw#1iEe5~XvniKhJ6Fz zBBnss-xvlrziz(YTqcbU81^i#K6gdEoARmlVI9u^>4M2T;5K$4J}eD$r-QZpZqwpj z2pLOEoUksz>4yMG2c+RK(6ed_`2O4X@#gy%c;($k*e@qyEx$(A*g<)KTPL;CtC;5O zIC;Fv_MlQ)$F*I;&1(ye)mn w0+|OXD)0-4gMC{lRPayI=Vfe)03Su*=E1n=oe)-Bsc`iVoM402uZ( z!7wyBI#^IQNGn*U*E^RNWmB8mLpO>ohjvvvdSk(s*Mxw8i$lRb{ueLfxOJ>KSQ41m z(4AgAsdy*Gy95$Sf`Y3FFuFQ_bp`G`IF9|>y&2)e&SMYZ xo`i4=ze-t=F9ybei zu$3Y%H!zFF+iorr t-gv8Yy;Y@f%XfN@$~X$35gL9> z7a-HFB^fix{jmUyc0HO&<>~J1RvN@K9Kj)wNZ97EHGKE=`*`K;2UwHMJ-ljB3$hM* zfkDL|bOvD_P7ibr2Mm}<45L?y?Dp$~GJEG9mu+KkhX)2Q0B>8tvY4ByW6$29oK*O& zaY&paaDKJnk6-!_fByPCESWPX-~>){ct_bu_>O217^I>mlH$|rm1GR;AUq&iKAl _@ICTQdDc7wYhic$r?_sw_k&fP2g$G`M(Jac2mb+{+`0}>t*U2=Ql z0wmiNQ@cW2gCIf!1#~ex_ZW`}!hFT4v#mUkd7|^r7fymL6ezXetKWSe@83P**{9YK zgq<$#LRY|50uGs|odMnp?@q_C9TRzOU2!ZrAXbr6zb%a9?Mt-iTmVS4A$y ~4fRkgOQKOn2ek~1(iZ&zxR=^Bqn9Ra12pX&qE*-&;3F=h( zC}><%D?+HuDY7ZEs&|U8)7zc!672VC?IW;yTy)`aNO2U39z%NdJuhuQY`rN;EeK-8 zsxjF7vuK;MUOxJYxI~hgf(&}E#2E4N*PX`tkzP+qAQxYI<-u2CKn_+q%mI!L+NU~= z(}tk|4Bj3nE9l8X7Ktm`Eto81NVvFVcpm}dBDnFQ50JbHS=Pl|HM>>nOjNBamNfB6 zfB1dyKWKD;Lr`C1>o=A#1`pBcdfHYS>eis3ok-mrs$ta@qaYyIhPaAsK3cU@&UeI= z+a2i9mDKZM2VqymmKw9`U{{U~Mc>Rv$o?>;h-2ya{TJTD4}SCjyOjsdtL &|3;*pLkMb5@+>1bqQ9f!G`4kwyM)g zLormno_Iy(?4VXfl1cX#_-tSX%W%@QNfdnT#oKuP=8FI5mp(CuvH%s~y?5Jl9uT9N z5ElUGd|QDNmZ9ktTF3FY0Se@k4!G7z2W&e(-CIoStYe}Y9hNYA?|A3#6<&Dl4xYX- z-3U@3i_N~W%W5rYIFvrk5g82U&x>6ThZGU!buLY@+oe%(W(%zDCJ=(a-@LXy);R!1 zh*2HT^0ga#lw&ouI=`w9oPjVjRC>~trW7F|0gIw+ZLr!Pod31%qCPo+pSi&^$cgnw zQ>ZAmqt6m1{GIGGGePwMS&prMLAcq|lPB9Sh;&Pa(+bM3Ckvce*!b+StGR}IY>E*f z>L?1EcI=Y7UK4A~>q1{NRcPSn-k *@{M&q2y0yjoB+&aPyRkzDOL^4!x1EmSqnP`;;HoM zsRrd3>{r}ITE{@oQ6#VHe6U94ETcHL52jHm*4jrG_{MwZ_}-g$v94i4Mm`i)SCs=! zZ>2wz9ZEX@wX 0fKEe1OV z$FZ$2=n8!6)w}rJZ@h&V1gpEL%Dg_~J4VwUK}W)aU2Fdy{ 7M_{=9+ zFl#4_m)i8?clzC$a{+7MOlv|LnAhHWggfWQxg^Ws4rQPmgb)BQ>NNMJgvZWCEyb=S zpPQ?T18Sv)gb `IiskWln&k-=y z>`sPJyKde{24UhBE2_5A#x?QX !*vw2YT&|s9qaE0tt=OHd25n6yo!>JWxGq?C8E$Y2a6b#*&-F!< z4C6`M1%)BRkswGno~{CFpHmg4v72%T34+BSw^GW`5UJ8kR<)y)&5AE8m~CW`mxSGp zK66}%$GLXv9p@MIDgCYA*!^TC_{(2UoPq3boc(Uh=^W4-u)(4Gz|iBxl;#75F$!o` z!04K8tk^pbk@M8~ CWW4(<%g{%C5_e1RNsv4}i zF; s({ODxVeqJ9n<|tyexot!A%pnZbi;sayaS#VxZq`*29anKis0R`cjdX997? zt6}1bTh|xv6A|HcYTV8^9vTbMUg3>k6xiP9HBHMI`(EpsAy9n(M-T8%zWPI~nGX}| z%BfB9a@gOeG s1U`VDpuVdXasmn;j9G^X`?dr zFI2W_-C`V&tptH|#pR*mpMK+QeDjsNxOJU#lYp80sl>PQ>(A9m1+2hi0@)FdGU?Nv z=*ffOXS#Q>5ybXkxGlnRN 10H zWSj!t&e@v92o*u?4I$X_tkO&`LR6!ea5;sDye }QWal)@qN7Z&N-_uJGjH{KozwU&RP=fxwqQK zcMihJrg#3_q#T+^$p+3C6%fDgl5G>8C9D|CUIsJL)Y=fbqDvdqsH&O38Kgq^!ZxIZ zfaBKj-5=gZN`joMD+B=3WsKbtG6Oz`lT4m29Gnn<^6%R7?1?>zpen0o@93<#wd*&3 z2QYFL#C)F= AHx0;}H0|DQw#!{DvkKg$vS-&bG5H($MrXK!-1M6tP25DX8r0ZVoN zOtZ7ITQL;+4&OygqK02YhZI{I*&R72TinIYX24KFxH_4w{MDhD^Fm;p3EUpJ9V QEry8ilUFu{6@TWp2qtSLoL4`G+wejhSUZHK}0R zIwY!zloh3Rs?fV)+kB_Tf|OY3lD(#80hNFlvDJpsCUR10wZa^zt@mec?7t(w@mo%f z`ADxPCXnCyvpfGoSG{%r(eZ?RWNmG!HZ!nULE^za8sGYqwz^70hvH~e)w;`e=oMWn z#*XrW!Ln#a?jGTO3p!mM4^x13btrIaXg!qCvP%efba@0Fb(t)->Ow?CtDAj39lDXy z6y;*W)=ji$RT5%^+QII%L#1OYbuh+`+s22~#gWQ~f%=zj^M;DrykhR7Iq9wAcyS!N zBesj9B|B4_V|poh)f_}nTSslcz4Hw(zIG3JnTdlBFL!%Xd+@srG*b|yz@clgO?}PO zvjfHvhaYCt@W9-<;?9F3UVQUDe(Cd1bM}=Iw&Q#d#X8880u~W$tq;*9AlU5!sx8VQ zdk#i EW#i81@u|zRw zx%|Do13f6B!4EDr^oqT8g&3XJ98!kfXf`>5^Q(%lzIYqUm!HATG*sCz_?VMD#0^a= z)-{dKlb1|e809V#SKDvL0!a~B;|arw|32r4 %Rd#X9(21|rc$0;Ha3U6K_%rmL`pasJ9n0wM<0e#{jDZO|R| z(r7av>wubVArt|6<4kPH^vODZbl}X-l7=eLiM|m uOX&{TFVKH9MP0;XSJf +wk^2OyayY6&MRbfZFimmg zpn(d}hS{*$gmbrBqbn~Z`&O}vv43EDAq?~6p!RW-!Vtw~2H<~8xygXQr=U6iUb*xs zMjnJ1vsF#xXZubN0E2O(P!tqE!tkXUn7A!x%3=TjAOJ~3K~xBW?SzOaSQW%-sRIL- z?zX6A4n5{m=zG>ncNUqo*S(L2bu$;wI}eZe*Dt(>5M=~Q*Cx1eUoz^dSM0^xc7w+J zBe-#%&mh|AJ{B+-R0}F0FhLYw!X+kr>J(Y!oW_%KaVVrx#%PmO2HCb1xS^&)frqW( z#Wx?I*v{x`I)E6Q`pKZ(RM8xq5<+tW)`WFWc4j(gX)zExtB4)fuT7DnISELB3Rza& zm{yMGZ*3+ekG# 8hU*~02~;PH|D^u**&(^kVOcV2t){hbw_A)Eqcf@ipXZ!d9R)tq z0+ }!LBKF$w)2rIgpe$KA>vL;+zZVv0qq4Wh;53fBbC;a&9$yO#e!`ljC zv-HjnW6)Phox?ED!`?HiGXzxKXipxqF>n`N08?M#19~z3b57&;rx;LrgHP_nFi) (=S5wlSpk?StfTliQuw2;WlXwj=;K_~HR9omeW5 zWGXfY6Npqe{0BIF(U}z
Y4n;^{pC<>w%3d--2Gyj&e@rI>7!U(e7 zN?&aaA3QkNt_~($YCgpFj7))J6w-;kae!m>UEpkRdOdl?%+GY(8lJko;^8I5kRdS1 zmELi-#sTYfgVvz5w?EbGB ez4iqAf~Llf6b8P;#X zi pY4kGx*KRnwoDC41qbCa@(&Uuv70hGvOMSj7F{t%rE)_9fOFF^#{(;|>{X zk->k61g-5mO=A3;Q_JB6CC`NkEbUtHcksD%DoYFk?LIG