feat: Working version with google drive storage.

This commit is contained in:
charlesgauthereau
2026-01-24 12:13:55 +01:00
parent 4eeeef3427
commit a64a80910d
18 changed files with 397 additions and 562 deletions
@@ -250,4 +250,5 @@ export const ChannelForm = ({onSuccessAction, organization, defaultValues, kind}
</div>
</Form>
);
};
};
@@ -1,195 +1,3 @@
// 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 Googles 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";
@@ -197,33 +5,42 @@ import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/compon
import {Input} from "@/components/ui/input";
import {Separator} from "@/components/ui/separator";
import {Button} from "@/components/ui/button";
import {PasswordInput} from "@/components/ui/password-input";
import {
googleDriveRefreshTokenAction
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/google-drive/helpers";
import {toast} from "sonner";
type StorageGoogleDriveFormProps = {
form: UseFormReturn<any, any, any>;
form: UseFormReturn<any>;
};
export const StorageGoogleDriveForm = ({form}: StorageGoogleDriveFormProps) => {
const refreshToken = form.watch("config.refreshToken");
const isConnected = Boolean(refreshToken);
const handleConnect = () => {
const clientId = form.getValues("config.clientId");
const redirectUri = form.getValues("config.redirectUri") || `${window.location.origin}/api/google/drive/callback`;
const clientSecret = form.getValues("config.clientSecret");
const redirectUri = `${window.location.origin}/api/google/drive/callback`;
if (!clientId) {
alert("Please fill in Client ID first");
if (!clientId || !clientSecret) {
form.setError("config.clientId", {message: "Client ID and Secret are required"});
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`;
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) {
@@ -231,48 +48,49 @@ export const StorageGoogleDriveForm = ({form}: StorageGoogleDriveFormProps) => {
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) {
if (oauthWindow.location.href.startsWith(redirectUri)) {
const code = new URL(oauthWindow.location.href).searchParams.get("code");
if (!code) return;
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`;
const result = await googleDriveRefreshTokenAction({
code,
clientId,
clientSecret,
redirectUri,
});
const inner = result?.data;
// Exchange code for refresh token
const data = await googleDriveRefreshTokenAction({
code: code,
clientId: clientId,
clientSecret: clientSecret,
redirectUri: redirectUri,
})
if (inner?.success) {
toast.success(inner.actionSuccess?.message);
if (data.data.refreshToken) {
console.log(data.data.refreshToken)
form.setValue("config.refreshToken", data.data.refreshToken); // auto-update the field
if (!inner.value) {
form.setError("config", {message: "OAuth succeeded but no refresh token returned"});
return;
}
oauthWindow.close();
clearInterval(interval);
form.setValue("config.refreshToken", inner.value, {
shouldValidate: true,
shouldDirty: true,
});
} else {
toast.error(inner?.actionError?.message);
}
oauthWindow.close();
clearInterval(interval);
}
} catch (err) {
// ignore cross-origin errors until redirect happens
} catch {
// Ignore cross-origin errors until redirect
}
}, 500);
};
return (
<>
<Separator className="my-1"/>
{/* Client ID */}
<FormField
control={form.control}
name="config.clientId"
@@ -280,14 +98,13 @@ export const StorageGoogleDriveForm = ({form}: StorageGoogleDriveFormProps) => {
<FormItem>
<FormLabel>Client ID</FormLabel>
<FormControl>
<Input {...field} placeholder="XXXX.apps.googleusercontent.com"/>
<Input {...field} placeholder="xxxx.apps.googleusercontent.com"/>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
{/* Client Secret */}
<FormField
control={form.control}
name="config.clientSecret"
@@ -295,45 +112,13 @@ export const StorageGoogleDriveForm = ({form}: StorageGoogleDriveFormProps) => {
<FormItem>
<FormLabel>Client Secret</FormLabel>
<FormControl>
<Input {...field} placeholder="XXXXXXXXXXXX"/>
<PasswordInput {...field} />
</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"
@@ -341,30 +126,26 @@ export const StorageGoogleDriveForm = ({form}: StorageGoogleDriveFormProps) => {
<FormItem>
<FormLabel>Folder ID</FormLabel>
<FormControl>
<Input {...field} placeholder="XXXXXXXXXXXXXXXXXXXX"/>
<Input {...field} />
</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>
)}
/>
<input type="hidden" {...form.register("config.refreshToken")} />
<Button type="button" onClick={handleConnect}> Connect Google Drive </Button>
<div className="flex items-center gap-3">
<Button type="button" onClick={handleConnect}>
{isConnected ? "Reconnect Google Drive" : "Connect Google Drive"}
</Button>
{isConnected && (
<span className="text-sm text-green-600 font-medium">
Google Drive connected
</span>
)}
</div>
</>
);
};
@@ -1,8 +1,7 @@
"use server"
import {userAction} from "@/lib/safe-actions/actions";
import {string, z} from "zod";
import {google} from "googleapis";
import {z} from "zod";
import {ServerActionResult} from "@/types/action-type";
export const googleDriveRefreshTokenAction = userAction.schema(
@@ -11,29 +10,43 @@ export const googleDriveRefreshTokenAction = userAction.schema(
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);
})).action(async ({parsedInput}): Promise<ServerActionResult<string>> => {
const {code, clientId, clientSecret, redirectUri} = parsedInput;
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,
}),
});
try {
console.log(tokenRes);
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
code: code,
grant_type: "authorization_code",
redirect_uri: redirectUri,
}),
});
const tokens = await tokenRes.json();
return {
success: true,
value: tokens.refresh_token,
actionSuccess: {
message: "Refresh token successfully fetched",
messageParams: {code: code},
},
};
}catch {
return {
success: false,
actionError: {
message: "An error occurred",
status: 404,
messageParams: {code: code},
},
};
}
const tokens = await tokenRes.json();
console.log(tokens.refresh_token);
return {
refreshToken: tokens.refresh_token,
};
});
@@ -51,6 +51,10 @@ export const downloadBackupAction = userAction.schema(
path: backupStorage.path,
signedUrl: true,
},
metadata: {
storageId: backupStorage.storageChannelId,
fileKind: "backups"
}
};
console.log(input)