mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
feat: start working on google drive storage
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { google } from "googleapis";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const code = url.searchParams.get("code");
|
||||
const clientId = url.searchParams.get("clientId");
|
||||
const clientSecret = url.searchParams.get("clientSecret");
|
||||
const redirectUri = url.searchParams.get("redirectUri");
|
||||
|
||||
if (!code || !clientId || !clientSecret || !redirectUri) {
|
||||
return NextResponse.json({ error: "Missing parameters" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const oauth2Client = new google.auth.OAuth2(clientId, clientSecret, redirectUri);
|
||||
const { tokens } = await oauth2Client.getToken(code);
|
||||
|
||||
// Save tokens.refresh_token securely in your DB here
|
||||
|
||||
return NextResponse.json(tokens);
|
||||
} catch (err: any) {
|
||||
console.error("Error exchanging code:", err);
|
||||
return NextResponse.json({ error: "Failed to exchange code" }, { status: 500 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in GET handler:", error);
|
||||
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -62,6 +62,7 @@
|
||||
"drizzle-orm": "^0.43.1",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"googleapis": "^170.1.0",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.553.0",
|
||||
"minio": "^8.0.5",
|
||||
@@ -122,5 +123,5 @@
|
||||
"typescript": "^5.8.3",
|
||||
"zenstack": "2.14.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.27.0"
|
||||
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316"
|
||||
}
|
||||
|
||||
Generated
+259
@@ -152,6 +152,9 @@ importers:
|
||||
embla-carousel-react:
|
||||
specifier: ^8.6.0
|
||||
version: 8.6.0(react@19.2.3)
|
||||
googleapis:
|
||||
specifier: ^170.1.0
|
||||
version: 170.1.0
|
||||
input-otp:
|
||||
specifier: ^1.4.2
|
||||
version: 1.4.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -1735,6 +1738,10 @@ packages:
|
||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@prisma/client@6.7.0':
|
||||
resolution: {integrity: sha512-+k61zZn1XHjbZul8q6TdQLpuI/cvyfil87zqK2zpreNIXyXtpUv3+H/oM69hcsFcZXaokHJIzPAt5Z8C8eK2QA==}
|
||||
engines: {node: '>=18.18'}
|
||||
@@ -3430,6 +3437,10 @@ packages:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
ajv-formats@2.1.1:
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependencies:
|
||||
@@ -3630,6 +3641,9 @@ packages:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
bignumber.js@9.3.1:
|
||||
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
|
||||
|
||||
binary-extensions@2.3.0:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3668,6 +3682,9 @@ packages:
|
||||
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
@@ -3924,6 +3941,10 @@ packages:
|
||||
damerau-levenshtein@1.0.8:
|
||||
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
||||
|
||||
data-uri-to-buffer@4.0.1:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
data-view-buffer@1.0.2:
|
||||
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4161,6 +4182,9 @@ packages:
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
|
||||
effect@3.18.4:
|
||||
resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==}
|
||||
|
||||
@@ -4429,6 +4453,9 @@ packages:
|
||||
exsolve@1.0.8:
|
||||
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
|
||||
|
||||
extend@3.0.2:
|
||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||
|
||||
fast-check@3.23.2:
|
||||
resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
@@ -4483,6 +4510,10 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
@@ -4522,6 +4553,10 @@ packages:
|
||||
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
|
||||
engines: {node: '>=0.4.x'}
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
@@ -4575,6 +4610,14 @@ packages:
|
||||
functions-have-names@1.2.3:
|
||||
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
|
||||
|
||||
gaxios@7.1.3:
|
||||
resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
gcp-metadata@8.1.2:
|
||||
resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
generator-function@2.0.1:
|
||||
resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4625,6 +4668,10 @@ packages:
|
||||
glob-to-regexp@0.4.1:
|
||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||
|
||||
glob@10.5.0:
|
||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||
hasBin: true
|
||||
|
||||
glob@11.1.0:
|
||||
resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
|
||||
engines: {node: 20 || >=22}
|
||||
@@ -4646,6 +4693,22 @@ packages:
|
||||
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
google-auth-library@10.5.0:
|
||||
resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
google-logging-utils@1.1.3:
|
||||
resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
googleapis-common@8.0.1:
|
||||
resolution: {integrity: sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
googleapis@170.1.0:
|
||||
resolution: {integrity: sha512-RLbc7yG6qzZqvAmGcgjvNIoZ7wpcCFxtc+HN+46etxDrlO4a8l5Cb7NxNQGhV91oRmL7mt56VoRoypAtEQEIKg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4653,6 +4716,10 @@ packages:
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
gtoken@8.0.0:
|
||||
resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
has-bigints@1.1.0:
|
||||
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4707,6 +4774,10 @@ packages:
|
||||
resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
@@ -4900,6 +4971,9 @@ packages:
|
||||
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
|
||||
jackspeak@4.1.1:
|
||||
resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
@@ -4935,6 +5009,9 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
json-bigint@1.0.0:
|
||||
resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
|
||||
|
||||
json-buffer@3.0.1:
|
||||
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
||||
|
||||
@@ -4966,6 +5043,12 @@ packages:
|
||||
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
jwa@2.0.1:
|
||||
resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
|
||||
|
||||
jws@4.0.1:
|
||||
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
|
||||
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
@@ -5124,6 +5207,9 @@ packages:
|
||||
lowlight@1.17.0:
|
||||
resolution: {integrity: sha512-vmtBgYKD+QVNy7tIa7ulz5d//Il9R4MooOVh4nkOf9R9Cb/Dk5TXMSTieg/vDulkBkIWj59/BIlyFQxT9X1oAQ==}
|
||||
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
lru-cache@11.2.4:
|
||||
resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==}
|
||||
engines: {node: 20 || >=22}
|
||||
@@ -5364,6 +5450,11 @@ packages:
|
||||
resolution: {integrity: sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
node-fetch-native@1.6.7:
|
||||
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
|
||||
|
||||
@@ -5376,6 +5467,10 @@ packages:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
node-fetch@3.3.2:
|
||||
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
node-forge@1.3.3:
|
||||
resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==}
|
||||
engines: {node: '>= 6.13.0'}
|
||||
@@ -5530,6 +5625,10 @@ packages:
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
engines: {node: '>=16 || 14 >=14.18'}
|
||||
|
||||
path-scurry@2.0.1:
|
||||
resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
|
||||
engines: {node: 20 || >=22}
|
||||
@@ -5728,6 +5827,10 @@ packages:
|
||||
qr.js@0.0.0:
|
||||
resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==}
|
||||
|
||||
qs@6.14.1:
|
||||
resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
query-string@7.1.3:
|
||||
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -5928,6 +6031,10 @@ packages:
|
||||
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
|
||||
rimraf@5.0.10:
|
||||
resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==}
|
||||
hasBin: true
|
||||
|
||||
rou3@0.7.12:
|
||||
resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==}
|
||||
|
||||
@@ -6440,6 +6547,9 @@ packages:
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
url-template@2.0.8:
|
||||
resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==}
|
||||
|
||||
use-callback-ref@1.3.3:
|
||||
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6557,6 +6667,10 @@ packages:
|
||||
web-encoding@1.1.5:
|
||||
resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==}
|
||||
|
||||
web-streams-polyfill@3.3.3:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
@@ -8031,6 +8145,9 @@ snapshots:
|
||||
|
||||
'@phc/format@1.0.0': {}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@prisma/client@6.7.0(prisma@6.7.0(typescript@5.9.3))(typescript@5.9.3)':
|
||||
optionalDependencies:
|
||||
prisma: 6.7.0(typescript@5.9.3)
|
||||
@@ -10433,6 +10550,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ajv-formats@2.1.1(ajv@8.17.1):
|
||||
optionalDependencies:
|
||||
ajv: 8.17.1
|
||||
@@ -10640,6 +10759,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
zod: 4.3.5
|
||||
|
||||
bignumber.js@9.3.1: {}
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
bl@4.1.0:
|
||||
@@ -10681,6 +10802,8 @@ snapshots:
|
||||
|
||||
buffer-crc32@1.0.0: {}
|
||||
|
||||
buffer-equal-constant-time@1.0.1: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
@@ -10960,6 +11083,8 @@ snapshots:
|
||||
|
||||
damerau-levenshtein@1.0.8: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
||||
data-view-buffer@1.0.2:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -11119,6 +11244,10 @@ snapshots:
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
effect@3.18.4:
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
@@ -11650,6 +11779,8 @@ snapshots:
|
||||
|
||||
exsolve@1.0.8: {}
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
fast-check@3.23.2:
|
||||
dependencies:
|
||||
pure-rand: 6.1.0
|
||||
@@ -11702,6 +11833,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
dependencies:
|
||||
flat-cache: 4.0.1
|
||||
@@ -11739,6 +11875,10 @@ snapshots:
|
||||
|
||||
format@0.2.2: {}
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
framer-motion@12.23.22(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
@@ -11783,6 +11923,23 @@ snapshots:
|
||||
|
||||
functions-have-names@1.2.3: {}
|
||||
|
||||
gaxios@7.1.3:
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
node-fetch: 3.3.2
|
||||
rimraf: 5.0.10
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
gcp-metadata@8.1.2:
|
||||
dependencies:
|
||||
gaxios: 7.1.3
|
||||
google-logging-utils: 1.1.3
|
||||
json-bigint: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
generator-function@2.0.1: {}
|
||||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
@@ -11840,6 +11997,15 @@ snapshots:
|
||||
|
||||
glob-to-regexp@0.4.1: {}
|
||||
|
||||
glob@10.5.0:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
jackspeak: 3.4.3
|
||||
minimatch: 9.0.5
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
|
||||
glob@11.1.0:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
@@ -11860,10 +12026,48 @@ snapshots:
|
||||
define-properties: 1.2.1
|
||||
gopd: 1.2.0
|
||||
|
||||
google-auth-library@10.5.0:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
gaxios: 7.1.3
|
||||
gcp-metadata: 8.1.2
|
||||
google-logging-utils: 1.1.3
|
||||
gtoken: 8.0.0
|
||||
jws: 4.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
google-logging-utils@1.1.3: {}
|
||||
|
||||
googleapis-common@8.0.1:
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
gaxios: 7.1.3
|
||||
google-auth-library: 10.5.0
|
||||
qs: 6.14.1
|
||||
url-template: 2.0.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
googleapis@170.1.0:
|
||||
dependencies:
|
||||
google-auth-library: 10.5.0
|
||||
googleapis-common: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
gtoken@8.0.0:
|
||||
dependencies:
|
||||
gaxios: 7.1.3
|
||||
jws: 4.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
has-bigints@1.1.0: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
@@ -11923,6 +12127,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
@@ -12103,6 +12314,12 @@ snapshots:
|
||||
has-symbols: 1.1.0
|
||||
set-function-name: 2.0.2
|
||||
|
||||
jackspeak@3.4.3:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
jackspeak@4.1.1:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
@@ -12129,6 +12346,10 @@ snapshots:
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
|
||||
json-bigint@1.0.0:
|
||||
dependencies:
|
||||
bignumber.js: 9.3.1
|
||||
|
||||
json-buffer@3.0.1: {}
|
||||
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
@@ -12158,6 +12379,17 @@ snapshots:
|
||||
object.assign: 4.1.7
|
||||
object.values: 1.2.1
|
||||
|
||||
jwa@2.0.1:
|
||||
dependencies:
|
||||
buffer-equal-constant-time: 1.0.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
jws@4.0.1:
|
||||
dependencies:
|
||||
jwa: 2.0.1
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
keyv@4.5.4:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
@@ -12292,6 +12524,8 @@ snapshots:
|
||||
fault: 1.0.4
|
||||
highlight.js: 10.4.1
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@11.2.4: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
@@ -12494,12 +12728,20 @@ snapshots:
|
||||
|
||||
node-cron@4.2.1: {}
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-fetch-native@1.6.7: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
node-fetch@3.3.2:
|
||||
dependencies:
|
||||
data-uri-to-buffer: 4.0.1
|
||||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
|
||||
node-forge@1.3.3: {}
|
||||
|
||||
node-gyp-build@4.8.4: {}
|
||||
@@ -12680,6 +12922,11 @@ snapshots:
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
path-scurry@1.11.1:
|
||||
dependencies:
|
||||
lru-cache: 10.4.3
|
||||
minipass: 7.1.2
|
||||
|
||||
path-scurry@2.0.1:
|
||||
dependencies:
|
||||
lru-cache: 11.2.4
|
||||
@@ -12867,6 +13114,10 @@ snapshots:
|
||||
|
||||
qr.js@0.0.0: {}
|
||||
|
||||
qs@6.14.1:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
||||
query-string@7.1.3:
|
||||
dependencies:
|
||||
decode-uri-component: 0.2.2
|
||||
@@ -13131,6 +13382,10 @@ snapshots:
|
||||
|
||||
reusify@1.1.0: {}
|
||||
|
||||
rimraf@5.0.10:
|
||||
dependencies:
|
||||
glob: 10.5.0
|
||||
|
||||
rou3@0.7.12: {}
|
||||
|
||||
run-parallel@1.2.0:
|
||||
@@ -13840,6 +14095,8 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
url-template@2.0.8: {}
|
||||
|
||||
use-callback-ref@1.3.3(@types/react@19.0.10)(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
@@ -13977,6 +14234,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@zxing/text-encoding': 0.9.0
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
webpack-sources@3.3.3: {}
|
||||
|
||||
@@ -1,2 +1,13 @@
|
||||
onlyBuiltDependencies:
|
||||
- '@prisma/client'
|
||||
- '@prisma/engines'
|
||||
- argon2
|
||||
- bcrypt
|
||||
- cpu-features
|
||||
- esbuild
|
||||
- prisma
|
||||
- protobufjs
|
||||
- sharp
|
||||
- ssh2
|
||||
- unrs-resolver
|
||||
- zenstack
|
||||
|
||||
@@ -59,6 +59,7 @@ function checkRouteExists(pathname: string) {
|
||||
/^\/api\/events\/?$/,
|
||||
/^\/api\/init\/?$/,
|
||||
/^\/api\/config\/?$/,
|
||||
/^\/api\/google\/drive\/callback\/?$/,
|
||||
];
|
||||
return routePatterns.some((pattern) => pattern.test(pathname));
|
||||
}
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ export const NotificationChannelFormSchema = ChannelFormSchema.extend({
|
||||
});
|
||||
|
||||
export const StorageChannelFormSchema = ChannelFormSchema.extend({
|
||||
provider: z.enum(["local", "s3"], {required_error: "Provider is required"}),
|
||||
provider: z.enum(["local", "s3", "google-drive"], {required_error: "Provider is required"}),
|
||||
});
|
||||
|
||||
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ export const ChannelTestButton = ({channel, organizationId, kind}: NotifierTestC
|
||||
if (result.success) {
|
||||
toast.success("Successfully connected to storage channel");
|
||||
} else {
|
||||
console.error(result);
|
||||
toast.error("An error occurred while testing the storage channel, check your configuration");
|
||||
}
|
||||
} else {
|
||||
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
// import {UseFormReturn} from "react-hook-form";
|
||||
// import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
|
||||
// import {Input} from "@/components/ui/input";
|
||||
// import {Separator} from "@/components/ui/separator";
|
||||
//
|
||||
//
|
||||
// type StorageGoogleDriveFormProps = {
|
||||
// form: UseFormReturn<any, any, any>
|
||||
// }
|
||||
//
|
||||
//
|
||||
// export const StorageGoogleDriveForm = ({form}: StorageGoogleDriveFormProps) => {
|
||||
// return (
|
||||
// <>
|
||||
// <Separator className="my-1"/>
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="config.clientEmail"
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Client Email</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input {...field} placeholder="xxx@xxx.iam.gserviceaccount.com"/>
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="config.privateKey"
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Private Key</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input {...field} placeholder="xxxxxxxxxxxx"/>
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="config.folderId"
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Folder Id</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input {...field} placeholder="xxxxxxxxxxxx"/>
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
//
|
||||
// </>
|
||||
// )
|
||||
// }
|
||||
|
||||
// "use client";
|
||||
//
|
||||
// import {UseFormReturn} from "react-hook-form";
|
||||
// import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
|
||||
// import {Input} from "@/components/ui/input";
|
||||
// import {Separator} from "@/components/ui/separator";
|
||||
// import {Button} from "@/components/ui/button";
|
||||
// import {getServerUrl} from "@/utils/get-server-url";
|
||||
//
|
||||
// type StorageGoogleDriveFormProps = {
|
||||
// form: UseFormReturn<any, any, any>
|
||||
// };
|
||||
//
|
||||
// export const StorageGoogleDriveForm = ({form}: StorageGoogleDriveFormProps) => {
|
||||
// const handleConnect = async () => {
|
||||
// const clientId = form.getValues("config.clientId");
|
||||
// // const baseUrl = getServerUrl();
|
||||
// // const redirectUri = form.getValues("config.redirectUri") || "http://localhost:3000/oauth2callback";
|
||||
// const redirectUri = "http://localhost:8887/dashboard/storages/channels";
|
||||
//
|
||||
// if (!clientId) {
|
||||
// alert("Please fill in Client ID first");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Construct Google OAuth URL
|
||||
// const scope = encodeURIComponent("https://www.googleapis.com/auth/drive.file");
|
||||
// const url = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${scope}&access_type=offline&prompt=consent`;
|
||||
//
|
||||
// // Open consent screen in a new tab
|
||||
// window.open(url, "_blank");
|
||||
// };
|
||||
//
|
||||
// return (
|
||||
// <>
|
||||
// <Separator className="my-1"/>
|
||||
//
|
||||
// {/* Client ID */}
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="config.clientId"
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Client ID</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input {...field} placeholder="XXXX.apps.googleusercontent.com"/>
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
//
|
||||
// {/* Client Secret */}
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="config.clientSecret"
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Client Secret</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input {...field} placeholder="XXXXXXXXXXXX"/>
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
//
|
||||
// {/* Redirect URI */}
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="config.redirectUri"
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Redirect URI</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input {...field} placeholder="http://localhost:3000/oauth2callback"/>
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
//
|
||||
// {/* Refresh Token */}
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="config.refreshToken"
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Refresh Token</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input {...field} placeholder="Paste refresh token here"/>
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
//
|
||||
// {/* Folder ID */}
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="config.folderId"
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Folder ID</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input {...field} placeholder="XXXXXXXXXXXXXXXXXXXX"/>
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
//
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="oauth.connect"
|
||||
// render={() => (
|
||||
// <FormItem>
|
||||
// <FormLabel>OAuth2 Connect</FormLabel>
|
||||
// <FormControl>
|
||||
// <Button type="button" onClick={handleConnect}>
|
||||
// Connect Google Drive
|
||||
// </Button>
|
||||
// </FormControl>
|
||||
// <FormMessage>
|
||||
// Clicking this will open Google’s consent screen. After granting access, copy the authorization code and exchange it for a refresh token in your backend.
|
||||
// </FormMessage>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// </>
|
||||
// );
|
||||
// };
|
||||
//
|
||||
"use client";
|
||||
|
||||
import {UseFormReturn} from "react-hook-form";
|
||||
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {
|
||||
googleDriveRefreshTokenAction
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/google-drive/helpers";
|
||||
|
||||
type StorageGoogleDriveFormProps = {
|
||||
form: UseFormReturn<any, any, any>;
|
||||
};
|
||||
|
||||
export const StorageGoogleDriveForm = ({form}: StorageGoogleDriveFormProps) => {
|
||||
const handleConnect = () => {
|
||||
const clientId = form.getValues("config.clientId");
|
||||
const redirectUri = form.getValues("config.redirectUri") || `${window.location.origin}/api/google/drive/callback`;
|
||||
|
||||
if (!clientId) {
|
||||
alert("Please fill in Client ID first");
|
||||
return;
|
||||
}
|
||||
|
||||
const scope = encodeURIComponent("https://www.googleapis.com/auth/drive.file");
|
||||
const oauthUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${clientId}&redirect_uri=${encodeURIComponent(
|
||||
redirectUri
|
||||
)}&response_type=code&scope=${scope}&access_type=offline&prompt=consent`;
|
||||
|
||||
// Open OAuth in a new tab
|
||||
const oauthWindow = window.open(oauthUrl, "_blank", "width=500,height=600");
|
||||
|
||||
// Poll the window for the refresh token (after the redirect)
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
if (!oauthWindow || oauthWindow.closed) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if redirected to your server callback
|
||||
if (oauthWindow.location.href.startsWith(`${window.location.origin}/api/google/drive/callback`)) {
|
||||
const params = new URL(oauthWindow.location.href).searchParams;
|
||||
const code = params.get("code");
|
||||
console.log(code)
|
||||
if (code) {
|
||||
|
||||
const clientId = form.getValues("config.clientId");
|
||||
const clientSecret = form.getValues("config.clientSecret");
|
||||
const redirectUri =
|
||||
form.getValues("config.redirectUri") ||
|
||||
`${window.location.origin}/api/google/drive/callback`;
|
||||
|
||||
// Exchange code for refresh token
|
||||
const data = await googleDriveRefreshTokenAction({
|
||||
code: code,
|
||||
clientId: clientId,
|
||||
clientSecret: clientSecret,
|
||||
redirectUri: redirectUri,
|
||||
})
|
||||
|
||||
if (data.data.refreshToken) {
|
||||
console.log(data.data.refreshToken)
|
||||
form.setValue("config.refreshToken", data.data.refreshToken); // auto-update the field
|
||||
}
|
||||
|
||||
oauthWindow.close();
|
||||
clearInterval(interval);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore cross-origin errors until redirect happens
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Separator className="my-1"/>
|
||||
|
||||
{/* Client ID */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="config.clientId"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Client ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="XXXX.apps.googleusercontent.com"/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Client Secret */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="config.clientSecret"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Client Secret</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="XXXXXXXXXXXX"/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Redirect URI */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="config.redirectUri"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Redirect URI</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="http://localhost:3000/api/google-drive/callback"/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Refresh Token */}
|
||||
|
||||
{/*<FormField*/}
|
||||
{/* control={form.control}*/}
|
||||
{/* name="config.refreshToken"*/}
|
||||
{/* render={({field}) => (*/}
|
||||
{/* <FormItem>*/}
|
||||
{/* <FormLabel>Refresh Token</FormLabel>*/}
|
||||
{/* <FormControl>*/}
|
||||
{/* <Input {...field} placeholder="Will be filled after OAuth flow"/>*/}
|
||||
{/* </FormControl>*/}
|
||||
{/* <FormMessage/>*/}
|
||||
{/* </FormItem>*/}
|
||||
{/* )}*/}
|
||||
{/*/>*/}
|
||||
|
||||
{/* Folder ID */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="config.folderId"
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Folder ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="XXXXXXXXXXXXXXXXXXXX"/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="config.refreshToken"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Refresh Token</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Will be filled after OAuth flow" readOnly />
|
||||
</FormControl>
|
||||
<FormMessage>
|
||||
The refresh token will automatically appear here after connecting.
|
||||
</FormMessage>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="button" onClick={handleConnect}> Connect Google Drive </Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
"use server"
|
||||
import {userAction} from "@/lib/safe-actions/actions";
|
||||
|
||||
import {string, z} from "zod";
|
||||
import {google} from "googleapis";
|
||||
|
||||
|
||||
export const googleDriveRefreshTokenAction = userAction.schema(
|
||||
z.object({
|
||||
code: z.string(),
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string(),
|
||||
redirectUri: z.string(),
|
||||
})).action(async ({parsedInput}) => {
|
||||
// google.auth.OAuth2
|
||||
// const oauth2Client = new google.auth.OAuth2(parsedInput.clientId,parsedInput.clientSecret, parsedInput.redirectUri);
|
||||
// const { tokens } = await oauth2Client.getToken(parsedInput.code);
|
||||
|
||||
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
client_id: parsedInput.clientId,
|
||||
client_secret: parsedInput.clientSecret,
|
||||
code: parsedInput.code,
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: parsedInput.redirectUri,
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(tokenRes);
|
||||
|
||||
const tokens = await tokenRes.json();
|
||||
console.log(tokens.refresh_token);
|
||||
|
||||
return {
|
||||
refreshToken: tokens.refresh_token,
|
||||
};
|
||||
});
|
||||
@@ -29,6 +29,9 @@ import {LucideProps} from "lucide-react";
|
||||
import {
|
||||
StorageS3Form
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/s3.form";
|
||||
import {
|
||||
StorageGoogleDriveForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/google-drive.form";
|
||||
|
||||
export type ChannelKind = "notification" | "storage";
|
||||
|
||||
@@ -86,6 +89,8 @@ export const renderChannelForm = (provider: string | undefined, form: UseFormRet
|
||||
return <NotifierWebhookForm form={form}/>;
|
||||
case "s3":
|
||||
return <StorageS3Form form={form}/>
|
||||
case "google-drive":
|
||||
return <StorageGoogleDriveForm form={form}/>
|
||||
case "local":
|
||||
return <></>
|
||||
default:
|
||||
|
||||
@@ -5,6 +5,7 @@ import {ProviderIconTypes} from "@/components/wrappers/dashboard/admin/channels/
|
||||
export const storageProviders: ProviderIconTypes[] = [
|
||||
{value: "local", label: "Local", icon: Server},
|
||||
{value: "s3", label: "S3", icon: S3Icon},
|
||||
{value: "google-drive", label: "Google Drive", icon: GoogleDriveIcon},
|
||||
{value: "blob", label: "Azure Blob Storage", icon: BlobIcon, preview: true},
|
||||
{value: "gcs", label: "Google Cloud Storage", icon: GCSIcon, preview: true},
|
||||
]
|
||||
@@ -18,12 +19,15 @@ export function S3Icon(props: SVGProps<SVGSVGElement>) {
|
||||
);
|
||||
}
|
||||
|
||||
export function GoogleDriveIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={256} height={199} {...props} viewBox="0 0 256 229"><path fill="#0066da" d="m19.354 196.034l11.29 19.5c2.346 4.106 5.718 7.332 9.677 9.678q17.009-21.591 23.68-33.137q6.77-11.717 16.641-36.655q-26.604-3.502-40.32-3.502q-13.165 0-40.322 3.502c0 4.545 1.173 9.09 3.519 13.196z"/><path fill="#ea4335" d="M215.681 225.212c3.96-2.346 7.332-5.572 9.677-9.677l4.692-8.064l22.434-38.855a26.57 26.57 0 0 0 3.518-13.196q-27.315-3.502-40.247-3.502q-13.899 0-40.248 3.502q9.754 25.075 16.422 36.655q6.724 11.683 23.752 33.137"/><path fill="#00832d" d="M128.001 73.311q19.68-23.768 27.125-36.655q5.996-10.377 13.196-33.137C164.363 1.173 159.818 0 155.126 0h-54.25C96.184 0 91.64 1.32 87.68 3.519q9.16 26.103 15.544 37.154q7.056 12.213 24.777 32.638"/><path fill="#2684fc" d="M175.36 155.42H80.642l-40.32 69.792c3.958 2.346 8.503 3.519 13.195 3.519h148.968c4.692 0 9.238-1.32 13.196-3.52z"/><path fill="#00ac47" d="M128.001 73.311L87.681 3.52c-3.96 2.346-7.332 5.571-9.678 9.677L3.519 142.224A26.57 26.57 0 0 0 0 155.42h80.642z"/><path fill="#ffba00" d="m215.242 77.71l-37.243-64.514c-2.345-4.106-5.718-7.331-9.677-9.677l-40.32 69.792l47.358 82.109h80.496c0-4.546-1.173-9.09-3.519-13.196z"/></svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function BlobIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
// <svg xmlns="http://www.w3.org/2000/svg" width={256} height={199} {...props} viewBox="0 0 24 24">
|
||||
// <path fill="currentColor"
|
||||
// d="M13.207.006a2.16 2.16 0 0 0-1.62.582a2.15 2.15 0 0 0-.095 3.035l3.408 3.55a3.042 3.042 0 0 1-.663 4.688l-.463.239V7.285a15.42 15.42 0 0 0-8.018 10.487v.017l6.549-3.328v7.621L13.779 24V13.682l.897-.463a4.443 4.443 0 0 0 1.22-7.03l-3.37-3.525a.75.75 0 0 1 .037-1.055a.75.75 0 0 1 1.056.038l.467.486l-.006.006l4.07 4.244a.057.057 0 0 0 .082 0a.06.06 0 0 0 0-.07l-3.14-5.143l-.149.143l.149-.145C14.494.393 13.829.054 13.207.006m-.902 9.865v2.994l-4.152 2.149a14 14 0 0 1 2.767-3.928a14 14 0 0 1 1.385-1.215"/>
|
||||
// </svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width={256} height={199} {...props} viewBox="0 0 48 48">
|
||||
<path fill="#2979ff"
|
||||
d="M32.66,7H15.34c-0.715,0-1.375,0.381-1.732,1l-8.66,15c-0.357,0.619-0.357,1.381,0,2l8.66,15c0.357,0.619,1.018,1,1.732,1H32.66c0.715,0,1.375-0.381,1.732-1l8.66-15c0.357-0.619,0.357-1.381,0-2l-8.66-15C34.035,7.381,33.375,7,32.66,7z"></path>
|
||||
|
||||
@@ -225,6 +225,13 @@
|
||||
"when": 1768931603846,
|
||||
"tag": "0031_chemical_edwin_jarvis",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 32,
|
||||
"version": "7",
|
||||
"when": 1769203863305,
|
||||
"tag": "0032_sparkling_thunderbolt_ross",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {z} from "zod";
|
||||
import {database} from "@/db/schema/07_database";
|
||||
|
||||
|
||||
export const providerStorageKindEnum = pgEnum('provider_storage_kind', ['local', 's3']);
|
||||
export const providerStorageKindEnum = pgEnum('provider_storage_kind', ['local', 's3', 'google-drive']);
|
||||
|
||||
export const storageChannel = pgTable('storage_channel', {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// import {drive_v3, google} from "googleapis";
|
||||
// import Drive = drive_v3.Drive;
|
||||
// import {GoogleDriveConfig} from "@/features/storages/providers/google-drive/types";
|
||||
//
|
||||
//
|
||||
// export async function getGoogleDriveClient(config: GoogleDriveConfig): Promise<Drive> {
|
||||
// const auth = new google.auth.JWT({
|
||||
// email: config.clientEmail,
|
||||
// key: config.privateKey?.replace(/\\n/g, "\n"),
|
||||
// scopes: ["https://www.googleapis.com/auth/drive"],
|
||||
// });
|
||||
//
|
||||
// return google.drive({
|
||||
// version: "v3",
|
||||
// auth,
|
||||
// });
|
||||
// }
|
||||
//
|
||||
//
|
||||
// export async function findFileByName(
|
||||
// drive: any,
|
||||
// name: string,
|
||||
// folderId: string
|
||||
// ): Promise<string | null> {
|
||||
// const res = await drive.files.list({
|
||||
// q: `name='${name}' and '${folderId}' in parents and trashed=false`,
|
||||
// fields: "files(id)",
|
||||
// pageSize: 1,
|
||||
// });
|
||||
//
|
||||
// return res.data.files?.[0]?.id ?? null;
|
||||
// }
|
||||
|
||||
import {drive_v3, google} from "googleapis";
|
||||
import {GoogleDriveConfig} from "@/features/storages/providers/google-drive/types";
|
||||
import Drive = drive_v3.Drive;
|
||||
import {getServerUrl} from "@/utils/get-server-url";
|
||||
|
||||
export async function getGoogleDriveClient(config: GoogleDriveConfig): Promise<Drive> {
|
||||
const baseUrl = getServerUrl();
|
||||
|
||||
const oauth2Client = new google.auth.OAuth2(
|
||||
config.clientId,
|
||||
config.clientSecret,
|
||||
// config.redirectUri
|
||||
baseUrl
|
||||
);
|
||||
|
||||
oauth2Client.setCredentials({
|
||||
refresh_token: config.refreshToken
|
||||
});
|
||||
|
||||
return google.drive({
|
||||
version: "v3",
|
||||
auth: oauth2Client
|
||||
});
|
||||
}
|
||||
|
||||
export async function findFileByName(
|
||||
drive: drive_v3.Drive,
|
||||
name: string,
|
||||
folderId: string
|
||||
): Promise<string | null> {
|
||||
const res = await drive.files.list({
|
||||
q: `name='${name}' and '${folderId}' in parents and trashed=false`,
|
||||
fields: "files(id)",
|
||||
pageSize: 1,
|
||||
supportsAllDrives: true,
|
||||
includeItemsFromAllDrives: true,
|
||||
});
|
||||
|
||||
return res.data.files?.[0]?.id ?? null;
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
"use server"
|
||||
import {StorageDeleteInput, StorageGetInput, StorageResult, StorageUploadInput} from '../../types';
|
||||
import {GoogleDriveConfig} from "@/features/storages/providers/google-drive/types";
|
||||
import {findFileByName, getGoogleDriveClient} from "@/features/storages/providers/google-drive/helpers";
|
||||
import {Readable} from "node:stream";
|
||||
|
||||
|
||||
// export async function uploadGoogleDrive(
|
||||
// config: GoogleDriveConfig,
|
||||
// input: { data: StorageUploadInput }
|
||||
// ): Promise<StorageResult> {
|
||||
// const client = await getGoogleDriveClient(config);
|
||||
//
|
||||
// const name = input.data.path;
|
||||
//
|
||||
// const existing = await findFileByName(client, name, config.folderId);
|
||||
// if (existing) {
|
||||
// return {
|
||||
// success: false,
|
||||
// provider: "google-drive",
|
||||
// error: "File already exists"
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// await client.files.create({
|
||||
// requestBody: {
|
||||
// name,
|
||||
// parents: [config.folderId],
|
||||
// },
|
||||
// media: {
|
||||
// body: input.data.file as Buffer,
|
||||
// },
|
||||
// });
|
||||
//
|
||||
//
|
||||
// return {
|
||||
// success: true,
|
||||
// provider: 'google-drive',
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// export async function getGoogleDrive(
|
||||
// config: GoogleDriveConfig,
|
||||
// input: { data: StorageGetInput }
|
||||
// ): Promise<StorageResult> {
|
||||
// const client = await getGoogleDriveClient(config);
|
||||
// const name = input.data.path;
|
||||
//
|
||||
// const fileId = await findFileByName(client, name, config.folderId);
|
||||
// if (!fileId) {
|
||||
// return {success: false, provider: "google-drive", error: "File not found"};
|
||||
// }
|
||||
//
|
||||
// const res = await client.files.get(
|
||||
// {fileId, alt: "media"},
|
||||
// {responseType: "arraybuffer"}
|
||||
// );
|
||||
//
|
||||
// return {
|
||||
// success: true,
|
||||
// provider: "google-drive",
|
||||
// file: Buffer.from(res.data as ArrayBuffer),
|
||||
// };
|
||||
// }
|
||||
//
|
||||
//
|
||||
// export async function deleteGoogleDrive(
|
||||
// config: GoogleDriveConfig,
|
||||
// input: { data: StorageDeleteInput }
|
||||
// ): Promise<StorageResult> {
|
||||
// const client = await getGoogleDriveClient(config);
|
||||
// const name = input.data.path;
|
||||
//
|
||||
// const fileId = await findFileByName(client, name, config.folderId);
|
||||
// if (!fileId) {
|
||||
// return {success: false, provider: "google-drive", error: "File not found"};
|
||||
// }
|
||||
//
|
||||
// await client.files.delete({fileId});
|
||||
//
|
||||
// return {
|
||||
// success: true,
|
||||
// provider: "google-drive"
|
||||
// };
|
||||
// }
|
||||
//
|
||||
//
|
||||
// export async function pingGoogleDrive(config: GoogleDriveConfig): Promise<StorageResult> {
|
||||
// try {
|
||||
// const drive = await getGoogleDriveClient(config);
|
||||
// const name = `ping-${Date.now()}.txt`;
|
||||
//
|
||||
// const buffer = Buffer.from("ping");
|
||||
//
|
||||
// const file = await drive.files.create({
|
||||
// requestBody: {
|
||||
// name,
|
||||
// parents: [config.folderId],
|
||||
// },
|
||||
// media: {
|
||||
// mimeType: "text/plain",
|
||||
// body: Readable.from(buffer),
|
||||
// },
|
||||
// fields: "id",
|
||||
// });
|
||||
// console.log(file)
|
||||
//
|
||||
// await drive.files.get({fileId: file.data.id!});
|
||||
// await drive.files.delete({fileId: file.data.id!});
|
||||
//
|
||||
// return {
|
||||
// success: true,
|
||||
// provider: "google-drive",
|
||||
// response: "Google Drive storage OK",
|
||||
// };
|
||||
// } catch (err: any) {
|
||||
// return {
|
||||
// success: false,
|
||||
// provider: "google-drive",
|
||||
// response: err.message,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
export async function uploadGoogleDrive(
|
||||
config: GoogleDriveConfig,
|
||||
input: { data: StorageUploadInput }
|
||||
): Promise<StorageResult> {
|
||||
const client = await getGoogleDriveClient(config);
|
||||
const name = input.data.path;
|
||||
|
||||
const existing = await findFileByName(client, name, config.folderId);
|
||||
if (existing) return {success: false, provider: "google-drive", error: "File already exists"};
|
||||
|
||||
await client.files.create({
|
||||
requestBody: {name, parents: [config.folderId]},
|
||||
media: {body: Readable.from(input.data.file as Buffer)},
|
||||
fields: "id",
|
||||
supportsAllDrives: true,
|
||||
});
|
||||
|
||||
return {success: true, provider: 'google-drive'};
|
||||
}
|
||||
|
||||
export async function getGoogleDrive(
|
||||
config: GoogleDriveConfig,
|
||||
input: { data: StorageGetInput }
|
||||
): Promise<StorageResult> {
|
||||
const client = await getGoogleDriveClient(config);
|
||||
const name = input.data.path;
|
||||
|
||||
const fileId = await findFileByName(client, name, config.folderId);
|
||||
if (!fileId) return {success: false, provider: "google-drive", error: "File not found"};
|
||||
|
||||
const res = await client.files.get(
|
||||
{fileId, alt: "media", supportsAllDrives: true},
|
||||
{responseType: "arraybuffer"}
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
provider: "google-drive",
|
||||
file: Buffer.from(res.data as ArrayBuffer),
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteGoogleDrive(
|
||||
config: GoogleDriveConfig,
|
||||
input: { data: StorageDeleteInput }
|
||||
): Promise<StorageResult> {
|
||||
const client = await getGoogleDriveClient(config);
|
||||
const name = input.data.path;
|
||||
|
||||
const fileId = await findFileByName(client, name, config.folderId);
|
||||
if (!fileId) return {success: false, provider: "google-drive", error: "File not found"};
|
||||
|
||||
await client.files.delete({fileId, supportsAllDrives: true});
|
||||
|
||||
return {success: true, provider: "google-drive"};
|
||||
}
|
||||
|
||||
export async function pingGoogleDrive(config: GoogleDriveConfig): Promise<StorageResult> {
|
||||
try {
|
||||
const drive = await getGoogleDriveClient(config);
|
||||
const name = `ping-${Date.now()}.txt`;
|
||||
const buffer = Buffer.from("ping");
|
||||
|
||||
const file = await drive.files.create({
|
||||
requestBody: {name, parents: [config.folderId]},
|
||||
media: {mimeType: "text/plain", body: Readable.from(buffer)},
|
||||
fields: "id",
|
||||
supportsAllDrives: true,
|
||||
});
|
||||
|
||||
await drive.files.get({fileId: file.data.id!, supportsAllDrives: true});
|
||||
// await drive.files.delete({fileId: file.data.id!, supportsAllDrives: true});
|
||||
|
||||
return {success: true, provider: "google-drive", response: "Google Drive storage OK"};
|
||||
} catch (err: any) {
|
||||
return {success: false, provider: "google-drive", response: err.message};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
// export type GoogleDriveConfig = {
|
||||
// clientEmail: string;
|
||||
// privateKey: string;
|
||||
// folderId: string;
|
||||
// };
|
||||
|
||||
|
||||
export type GoogleDriveConfig = {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
refreshToken: string; // from OAuth flow
|
||||
// redirectUri: string; // e.g., http://localhost:3000/oauth2callback
|
||||
folderId: string; // target folder in your Drive
|
||||
};
|
||||
@@ -6,6 +6,12 @@ import type {
|
||||
|
||||
import {uploadLocal, getLocal, deleteLocal, pingLocal} from './local';
|
||||
import {deleteS3, getS3, pingS3, uploadS3} from "@/features/storages/providers/s3";
|
||||
import {
|
||||
deleteGoogleDrive,
|
||||
getGoogleDrive,
|
||||
pingGoogleDrive,
|
||||
uploadGoogleDrive
|
||||
} from "@/features/storages/providers/google-drive";
|
||||
|
||||
type ProviderHandler = {
|
||||
upload: (config: any, input: StorageInput & { action: 'upload' }) => Promise<StorageResult>;
|
||||
@@ -27,6 +33,12 @@ const handlers: Record<StorageProviderKind, ProviderHandler> = {
|
||||
delete: deleteS3,
|
||||
ping: pingS3,
|
||||
},
|
||||
"google-drive": {
|
||||
upload: uploadGoogleDrive,
|
||||
get: getGoogleDrive,
|
||||
delete: deleteGoogleDrive,
|
||||
ping: pingGoogleDrive,
|
||||
}
|
||||
// gcs: null as any,
|
||||
// azure: null as any,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type StorageProviderKind =
|
||||
| 'local'
|
||||
| 's3'
|
||||
| 'google-drive'
|
||||
;
|
||||
|
||||
export type StorageAction =
|
||||
|
||||
Reference in New Issue
Block a user