diff --git a/docs/API.md b/docs/API.md index e328d87..4285e94 100644 --- a/docs/API.md +++ b/docs/API.md @@ -15,6 +15,8 @@ This document provides a comprehensive reference for all functions and classes a - [createWhiteLabelGDriveConnector](#createwhitelabelgdriveconnector) - [createVectorizeDropboxConnector](#createvectorizedropboxconnector) - [createWhiteLabelDropboxConnector](#createwhitelabeldropboxconnector) + - [createVectorizeNotionConnector](#createvectorizenotionconnector) + - [createWhiteLabelNotionConnector](#createwhitelabelnotionconnector) - [Base API Functions](#base-api-functions) - [createSourceConnector](#createsourceconnector) - [manageUser](#manageuser) @@ -24,6 +26,9 @@ This document provides a comprehensive reference for all functions and classes a - [refreshGDriveToken](#refreshgdrivetoken) - [exchangeDropboxCodeForTokens](#exchangedropboxcodefortokens) - [refreshDropboxToken](#refreshdropboxtoken) + - [exchangeNotionCodeForTokens](#exchangenotioncodefortokens) + - [refreshNotionToken](#refreshnotiontoken) + - [manageNotionUser](#managenotionuser) ## OAuth Classes @@ -351,8 +356,8 @@ async function createVectorizeGDriveConnector( **Parameters:** - `config`: A `VectorizeAPIConfig` object containing: - - `authorization`: Bearer token for authentication (use VECTORIZE_TOKEN env var) - - `organizationId`: Your Vectorize organization ID (use VECTORIZE_ORG env var) + - `authorization`: Bearer token for authentication (use VECTORIZE_API_KEY env var) + - `organizationId`: Your Vectorize organization ID (use VECTORIZE_ORGANIZATION_ID env var) - `connectorName`: Name for the connector - `platformUrl` (optional): URL of the Vectorize API (defaults to "https://api.vectorize.io/v1") @@ -364,8 +369,8 @@ async function createVectorizeGDriveConnector( ```typescript const config = { - authorization: process.env.VECTORIZE_TOKEN!, - organizationId: process.env.VECTORIZE_ORG!, + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, }; const connectorId = await createVectorizeGDriveConnector( @@ -404,8 +409,8 @@ async function createWhiteLabelGDriveConnector( ```typescript const config = { - authorization: process.env.VECTORIZE_TOKEN!, - organizationId: process.env.VECTORIZE_ORG!, + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, }; const connectorId = await createWhiteLabelGDriveConnector( @@ -442,8 +447,8 @@ async function createVectorizeDropboxConnector( ```typescript const config = { - authorization: process.env.VECTORIZE_TOKEN!, - organizationId: process.env.VECTORIZE_ORG!, + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, }; const connectorId = await createVectorizeDropboxConnector( @@ -482,8 +487,8 @@ async function createWhiteLabelDropboxConnector( ```typescript const config = { - authorization: process.env.VECTORIZE_TOKEN!, - organizationId: process.env.VECTORIZE_ORG!, + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, }; const connectorId = await createWhiteLabelDropboxConnector( @@ -494,6 +499,140 @@ const connectorId = await createWhiteLabelDropboxConnector( ); ``` +### createVectorizeNotionConnector + +Creates a Notion connector using Vectorize's managed OAuth credentials. + +```typescript +async function createVectorizeNotionConnector( + config: VectorizeAPIConfig, + connectorName: string, + platformUrl?: string +): Promise +``` + +**Parameters:** + +- `config`: A `VectorizeAPIConfig` object +- `connectorName`: Name for the connector +- `platformUrl` (optional): URL of the Vectorize API (defaults to "https://api.vectorize.io/v1") + +**Returns:** + +- `Promise`: The ID of the created connector + +**Example:** + +```typescript +const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, +}; + +const connectorId = await createVectorizeNotionConnector( + config, + "My Notion Connector" +); +``` + +### createWhiteLabelNotionConnector + +Creates a Notion connector using your own OAuth credentials. + +```typescript +async function createWhiteLabelNotionConnector( + config: VectorizeAPIConfig, + connectorName: string, + clientId: string, + clientSecret: string, + platformUrl?: string +): Promise +``` + +**Parameters:** + +- `config`: A `VectorizeAPIConfig` object +- `connectorName`: Name for the connector +- `clientId`: Your Notion OAuth client ID +- `clientSecret`: Your Notion OAuth client secret +- `platformUrl` (optional): URL of the Vectorize API (defaults to "https://api.vectorize.io/v1") + +**Returns:** + +- `Promise`: The ID of the created connector + +**Example:** + +```typescript +const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, +}; + +const connectorId = await createWhiteLabelNotionConnector( + config, + "My Custom Notion Connector", + process.env.NOTION_CLIENT_ID!, + process.env.NOTION_CLIENT_SECRET! +); +``` + +### manageNotionUser + +Manages a Notion user for a connector, allowing you to add, edit, or remove users. + +```typescript +async function manageNotionUser( + config: VectorizeAPIConfig, + connectorId: string, + selectedPages: Record | null, + accessToken: string, + userId: string, + action: UserAction, + platformUrl?: string +): Promise +``` + +**Parameters:** + +- `config`: A `VectorizeAPIConfig` object +- `connectorId`: ID of the connector +- `selectedPages`: Record of selected pages with their metadata (required for add/edit actions) +- `accessToken`: Notion OAuth access token (required for add/edit actions) +- `userId`: User ID to manage +- `action`: Action to perform ("add", "edit", or "remove") +- `platformUrl` (optional): URL of the Vectorize API (defaults to "https://api.vectorize.io/v1") + +**Returns:** + +- `Promise`: The API response + +**Example:** + +```typescript +const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, +}; + +const selectedPages = { + "page1": { + title: "My Page", + pageId: "page1", + parentType: "workspace" + } +}; + +const response = await manageNotionUser( + config, + "connector123", + selectedPages, + "notion_access_token", + "user123", + "add" +); +``` + ## Base API Functions ### createSourceConnector @@ -653,6 +792,67 @@ console.log('New access token:', tokens.access_token); console.log('Expires in:', tokens.expires_in, 'seconds'); ``` +### exchangeNotionCodeForTokens + +Exchanges an authorization code for Notion OAuth tokens. + +```typescript +async function exchangeNotionCodeForTokens( + code: string, + clientId: string, + clientSecret: string, + redirectUri: string +): Promise +``` + +**Parameters:** + +- `code`: Authorization code from Notion OAuth flow +- `clientId`: Notion OAuth client ID +- `clientSecret`: Notion OAuth client secret +- `redirectUri`: Redirect URI used in the OAuth flow + +**Returns:** + +- `Promise`: Notion OAuth tokens + +**Example:** + +```typescript +import { exchangeNotionCodeForTokens } from '@vectorize-io/vectorize-connect'; + +const tokens = await exchangeNotionCodeForTokens( + authCode, + process.env.NOTION_CLIENT_ID!, + process.env.NOTION_CLIENT_SECRET!, + "https://yourapp.com/callback" +); +``` + +### refreshNotionToken + +Validates an existing Notion access token by making an API request to Notion. + +```typescript +async function refreshNotionToken(accessToken: string): Promise +``` + +**Parameters:** + +- `accessToken`: Notion access token to validate + +**Returns:** + +- `Promise`: Validated token information + +**Example:** + +```typescript +import { refreshNotionToken } from '@vectorize-io/vectorize-connect'; + +const validatedToken = await refreshNotionToken("notion_access_token"); +``` + ### exchangeDropboxCodeForTokens Exchanges an authorization code for access and refresh tokens. diff --git a/docs/authentication/README.md b/docs/authentication/README.md new file mode 100644 index 0000000..5470d8f --- /dev/null +++ b/docs/authentication/README.md @@ -0,0 +1,68 @@ +# Authentication + +This section covers authentication flows for different connector approaches. + +## Approaches + +- **[Vectorize](./vectorize/)** - Authentication using Vectorize's managed OAuth flow +- **[White-Label](./white-label/)** - Authentication using your own OAuth applications + +## Overview + +Authentication is the process of connecting users to their cloud storage accounts and obtaining the necessary permissions to access their files. + +### Vectorize Approach +- Users are redirected to Vectorize's platform for authentication +- Vectorize handles the OAuth flow and token management +- Simplified implementation with consistent user experience + +### White-Label Approach +- Users authenticate directly with your OAuth application +- Full control over the authentication experience +- Requires implementing OAuth callback handling + +## Platform-Specific Guides + +### Vectorize Approach +- [Google Drive Authentication](./vectorize/google-drive.md) +- [Dropbox Authentication](./vectorize/dropbox.md) +- [Notion Authentication](./vectorize/notion.md) + +### White-Label Approach +- [Google Drive Authentication](./white-label/google-drive.md) +- [Dropbox Authentication](./white-label/dropbox.md) +- [Notion Authentication](./white-label/notion.md) + +## Quick Reference + +### Vectorize Authentication Flow +```typescript +import { getOneTimeConnectorToken, GoogleDriveOAuth } from '@vectorize-io/vectorize-connect'; + +// 1. Generate one-time token +const tokenResponse = await getOneTimeConnectorToken(config, userId, connectorId); + +// 2. Redirect to Vectorize authentication (example with Google Drive) +await GoogleDriveOAuth.redirectToVectorizeConnect( + tokenResponse.token, + organizationId +); +``` + +### White-Label Authentication Flow +```typescript +import { GoogleDriveOAuth } from '@vectorize-io/vectorize-connect'; + +// Start OAuth flow in popup (example with Google Drive) +GoogleDriveOAuth.startOAuth({ + clientId: process.env.GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET!, + redirectUri: `${window.location.origin}/api/oauth/callback`, + onSuccess: (response) => { + // Handle successful authentication + }, + onError: (error) => { + // Handle authentication error + } +}); +``` diff --git a/docs/authentication/vectorize/README.md b/docs/authentication/vectorize/README.md new file mode 100644 index 0000000..e14fbc1 --- /dev/null +++ b/docs/authentication/vectorize/README.md @@ -0,0 +1,184 @@ +# Authentication - Vectorize Approach + +Authenticate users using Vectorize's managed OAuth flow. + +## One-Time Token Generation + +Create an API endpoint to generate one-time tokens securely on the server: + +```typescript +// app/api/get-one-time-connector-token/route.ts +import { getOneTimeConnectorToken, VectorizeAPIConfig } from "@vectorize-io/vectorize-connect"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(request: NextRequest) { + try { + // Get authentication details from environment variables + const apiKey = process.env.VECTORIZE_API_KEY; + const organizationId = process.env.VECTORIZE_ORGANIZATION_ID; + + if (!apiKey || !organizationId) { + return NextResponse.json({ + error: 'Missing Vectorize API configuration' + }, { status: 500 }); + } + + // Configure the Vectorize API client + const config: VectorizeAPIConfig = { + authorization: apiKey, + organizationId: organizationId + }; + + // Get userId and connectorId from request url + const { searchParams } = new URL(request.url); + const userId = searchParams.get('userId'); + const connectorId = searchParams.get('connectorId'); + + // Validate userId and connectorId + if (!userId || !connectorId) { + return NextResponse.json({ + error: 'Missing userId or connectorId' + }, { status: 400 }); + } + + // Call Vectorize API to get the token + const tokenResponse = await getOneTimeConnectorToken( + config, + userId, + connectorId + ); + + // Return the token to the client + return NextResponse.json(tokenResponse, { status: 200 }); + + } catch (error) { + console.error('Error generating token:', error); + return NextResponse.json({ + error: 'Failed to generate token', + message: error instanceof Error ? error.message : 'Unknown error' + }, { status: 500 }); + } +} +``` + +## Frontend Authentication Flow + +```typescript +import { PlatformOAuth } from '@vectorize-io/vectorize-connect'; + +const handleAuthenticate = async () => { + try { + // Get one-time token from API endpoint + const tokenResponse = await fetch( + `/api/get-one-time-connector-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize authentication flow + await PlatformOAuth.redirectToVectorizeConnect( + tokenResponse.token, + organizationId + ); + + } catch (error) { + console.error('Authentication failed:', error); + } +}; +``` + +## Complete Component Example + +```typescript +'use client'; + +import { useState } from 'react'; +import { PlatformOAuth } from '@vectorize-io/vectorize-connect'; + +export default function VectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleConnect = async () => { + setIsLoading(true); + setError(null); + + try { + // Get one-time token from API endpoint + const tokenResponse = await fetch( + `/api/get-one-time-connector-token?userId=user123&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize authentication flow + await PlatformOAuth.redirectToVectorizeConnect( + tokenResponse.token, + 'your-org-id' + ); + + setIsLoading(false); + } catch (err: any) { + const errorMessage = err instanceof Error ? err.message : 'Failed to connect'; + setError(errorMessage); + setIsLoading(false); + } + }; + + return ( +
+

Platform Connection

+ + {error && ( +
+ {error} +
+ )} + + +
+ ); +} +``` + +## Error Handling + +```typescript +try { + const tokenResponse = await getOneTimeConnectorToken(config, userId, connectorId); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API token'); + } else if (error.response?.status === 404) { + console.error('Connector or user not found'); + } else { + console.error('Token generation failed:', error.message); + } +} +``` + +## Platform-Specific Examples + +For detailed platform-specific authentication examples: + +- [Google Drive Authentication](./google-drive.md) +- [Dropbox Authentication](./dropbox.md) +- [Notion Authentication](./notion.md) + +## Next Steps + +- [User Management](../../user-management/vectorize/) +- [Frontend Implementation](../../frontend-implementation/vectorize/) diff --git a/docs/authentication/vectorize/dropbox.md b/docs/authentication/vectorize/dropbox.md new file mode 100644 index 0000000..e3aed66 --- /dev/null +++ b/docs/authentication/vectorize/dropbox.md @@ -0,0 +1,168 @@ +# Dropbox Authentication - Vectorize Approach + +Authenticate users with Dropbox using Vectorize's managed OAuth flow. + +## Environment Setup + +```bash +# Required environment variables +VECTORIZE_API_KEY=your_vectorize_api_key +VECTORIZE_ORGANIZATION_ID=your_organization_id +``` + +## One-Time Token Generation + +Create an API endpoint to generate one-time tokens for Dropbox authentication: + +```typescript +// app/api/get-dropbox-token/route.ts +import { getOneTimeConnectorToken, VectorizeAPIConfig } from "@vectorize-io/vectorize-connect"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(request: NextRequest) { + try { + const config: VectorizeAPIConfig = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! + }; + + const { searchParams } = new URL(request.url); + const userId = searchParams.get('userId'); + const connectorId = searchParams.get('connectorId'); + + if (!userId || !connectorId) { + return NextResponse.json({ + error: 'Missing userId or connectorId' + }, { status: 400 }); + } + + const tokenResponse = await getOneTimeConnectorToken( + config, + userId, + connectorId + ); + + return NextResponse.json(tokenResponse, { status: 200 }); + + } catch (error) { + console.error('Error generating Dropbox token:', error); + return NextResponse.json({ + error: 'Failed to generate token' + }, { status: 500 }); + } +} +``` + +## Frontend Authentication Flow + +```typescript +import { DropboxOAuth } from '@vectorize-io/vectorize-connect'; + +const handleDropboxAuth = async () => { + try { + // Get one-time token from API endpoint + const tokenResponse = await fetch( + `/api/get-dropbox-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize Dropbox authentication + await DropboxOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + } catch (error) { + console.error('Dropbox authentication failed:', error); + } +}; +``` + +## Complete Component Example + +```typescript +'use client'; + +import { useState } from 'react'; +import { DropboxOAuth } from '@vectorize-io/vectorize-connect'; + +export default function DropboxVectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleConnect = async () => { + setIsLoading(true); + setError(null); + + try { + // Get one-time token + const tokenResponse = await fetch( + `/api/get-dropbox-token?userId=user123&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize authentication + await DropboxOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + setIsLoading(false); + } catch (err: any) { + const errorMessage = err instanceof Error ? err.message : 'Failed to connect'; + setError(errorMessage); + setIsLoading(false); + } + }; + + return ( +
+

Dropbox Vectorize Connection

+ + {error && ( +
+ {error} +
+ )} + + +
+ ); +} +``` + +## Error Handling + +```typescript +try { + const tokenResponse = await getOneTimeConnectorToken(config, userId, connectorId); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API credentials'); + } else if (error.response?.status === 404) { + console.error('Dropbox connector or user not found'); + } else { + console.error('Token generation failed:', error.message); + } +} +``` + +## Next Steps + +- [Dropbox User Management](../../user-management/vectorize/dropbox.md) +- [Frontend Implementation](../../frontend-implementation/vectorize/) diff --git a/docs/authentication/vectorize/google-drive.md b/docs/authentication/vectorize/google-drive.md new file mode 100644 index 0000000..e46f3ef --- /dev/null +++ b/docs/authentication/vectorize/google-drive.md @@ -0,0 +1,168 @@ +# Google Drive Authentication - Vectorize Approach + +Authenticate users with Google Drive using Vectorize's managed OAuth flow. + +## Environment Setup + +```bash +# Required environment variables +VECTORIZE_API_KEY=your_vectorize_api_key +VECTORIZE_ORGANIZATION_ID=your_organization_id +``` + +## One-Time Token Generation + +Create an API endpoint to generate one-time tokens for Google Drive authentication: + +```typescript +// app/api/get-gdrive-token/route.ts +import { getOneTimeConnectorToken, VectorizeAPIConfig } from "@vectorize-io/vectorize-connect"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(request: NextRequest) { + try { + const config: VectorizeAPIConfig = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! + }; + + const { searchParams } = new URL(request.url); + const userId = searchParams.get('userId'); + const connectorId = searchParams.get('connectorId'); + + if (!userId || !connectorId) { + return NextResponse.json({ + error: 'Missing userId or connectorId' + }, { status: 400 }); + } + + const tokenResponse = await getOneTimeConnectorToken( + config, + userId, + connectorId + ); + + return NextResponse.json(tokenResponse, { status: 200 }); + + } catch (error) { + console.error('Error generating Google Drive token:', error); + return NextResponse.json({ + error: 'Failed to generate token' + }, { status: 500 }); + } +} +``` + +## Frontend Authentication Flow + +```typescript +import { GoogleDriveOAuth } from '@vectorize-io/vectorize-connect'; + +const handleGoogleDriveAuth = async () => { + try { + // Get one-time token from API endpoint + const tokenResponse = await fetch( + `/api/get-gdrive-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize Google Drive authentication + await GoogleDriveOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + } catch (error) { + console.error('Google Drive authentication failed:', error); + } +}; +``` + +## Complete Component Example + +```typescript +'use client'; + +import { useState } from 'react'; +import { GoogleDriveOAuth } from '@vectorize-io/vectorize-connect'; + +export default function GoogleDriveVectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleConnect = async () => { + setIsLoading(true); + setError(null); + + try { + // Get one-time token + const tokenResponse = await fetch( + `/api/get-gdrive-token?userId=user123&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize authentication + await GoogleDriveOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + setIsLoading(false); + } catch (err: any) { + const errorMessage = err instanceof Error ? err.message : 'Failed to connect'; + setError(errorMessage); + setIsLoading(false); + } + }; + + return ( +
+

Google Drive Vectorize Connection

+ + {error && ( +
+ {error} +
+ )} + + +
+ ); +} +``` + +## Error Handling + +```typescript +try { + const tokenResponse = await getOneTimeConnectorToken(config, userId, connectorId); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API credentials'); + } else if (error.response?.status === 404) { + console.error('Google Drive connector or user not found'); + } else { + console.error('Token generation failed:', error.message); + } +} +``` + +## Next Steps + +- [Google Drive User Management](../../user-management/vectorize/google-drive.md) +- [Frontend Implementation](../../frontend-implementation/vectorize/) diff --git a/docs/authentication/vectorize/notion.md b/docs/authentication/vectorize/notion.md new file mode 100644 index 0000000..2611b83 --- /dev/null +++ b/docs/authentication/vectorize/notion.md @@ -0,0 +1,192 @@ +# Notion Authentication - Vectorize Approach + +Authenticate users with Notion using Vectorize's managed OAuth flow. + +## Environment Setup + +```bash +# Required environment variables +VECTORIZE_API_KEY=your_vectorize_api_key +VECTORIZE_ORGANIZATION_ID=your_organization_id +``` + +## One-Time Token Generation + +Create an API endpoint to generate one-time tokens for Notion authentication: + +```typescript +// app/api/get-notion-token/route.ts +import { getOneTimeConnectorToken, VectorizeAPIConfig } from "@vectorize-io/vectorize-connect"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(request: NextRequest) { + try { + const config: VectorizeAPIConfig = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! + }; + + const { searchParams } = new URL(request.url); + const userId = searchParams.get('userId'); + const connectorId = searchParams.get('connectorId'); + + if (!userId || !connectorId) { + return NextResponse.json({ + error: 'Missing userId or connectorId' + }, { status: 400 }); + } + + const tokenResponse = await getOneTimeConnectorToken( + config, + userId, + connectorId + ); + + return NextResponse.json(tokenResponse, { status: 200 }); + + } catch (error) { + console.error('Error generating Notion token:', error); + return NextResponse.json({ + error: 'Failed to generate token' + }, { status: 500 }); + } +} +``` + +## Frontend Authentication Flow + +```typescript +import { NotionOAuth } from '@vectorize-io/vectorize-connect'; + +const handleNotionAuth = async () => { + try { + // Get one-time token from API endpoint + const tokenResponse = await fetch( + `/api/get-notion-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize Notion authentication + await NotionOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + } catch (error) { + console.error('Notion authentication failed:', error); + } +}; +``` + +## Complete Component Example + +```typescript +'use client'; + +import { useState } from 'react'; +import { NotionOAuth } from '@vectorize-io/vectorize-connect'; + +export default function NotionVectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleConnect = async () => { + setIsLoading(true); + setError(null); + + try { + // Get one-time token + const tokenResponse = await fetch( + `/api/get-notion-token?userId=user123&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize authentication + await NotionOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + setIsLoading(false); + } catch (err: any) { + const errorMessage = err instanceof Error ? err.message : 'Failed to connect'; + setError(errorMessage); + setIsLoading(false); + } + }; + + return ( +
+

Notion Vectorize Connection

+ + {error && ( +
+ {error} +
+ )} + + +
+ ); +} +``` + +## Edit Existing Connection + +For editing an existing Notion connection, use the edit flow: + +```typescript +const handleEditConnection = async () => { + try { + // Get one-time token for editing + const tokenResponse = await fetch( + `/api/get-notion-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => response.json()); + + // Redirect to Vectorize edit flow + await NotionOAuth.redirectToVectorizeEdit( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + } catch (error) { + console.error('Failed to edit Notion connection:', error); + } +}; +``` + +## Error Handling + +```typescript +try { + const tokenResponse = await getOneTimeConnectorToken(config, userId, connectorId); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API credentials'); + } else if (error.response?.status === 404) { + console.error('Notion connector or user not found'); + } else { + console.error('Token generation failed:', error.message); + } +} +``` + +## Next Steps + +- [Notion User Management](../../user-management/vectorize/notion.md) +- [Frontend Implementation](../../frontend-implementation/vectorize/) diff --git a/docs/authentication/white-label/README.md b/docs/authentication/white-label/README.md new file mode 100644 index 0000000..52e2b6f --- /dev/null +++ b/docs/authentication/white-label/README.md @@ -0,0 +1,354 @@ +# Authentication - White-Label Approach + +Authentication strategies for white-label connectors with custom OAuth flows. + +## Overview + +White-label authentication allows you to use your own OAuth applications and branding while integrating with cloud storage platforms. This approach gives you complete control over the user experience and OAuth credentials. + +## Prerequisites + +Before implementing white-label authentication, you need to set up OAuth applications for each platform: + +### Google Drive OAuth Setup +1. Go to [Google Cloud Console](https://console.cloud.google.com/) +2. Create a new project or select existing one +3. Enable Google Drive API +4. Create OAuth 2.0 credentials +5. Configure authorized redirect URIs + +### Dropbox OAuth Setup +1. Go to [Dropbox App Console](https://www.dropbox.com/developers/apps) +2. Create a new app +3. Choose "Scoped access" and "Full Dropbox" +4. Configure redirect URIs +5. Note your App key and App secret + +### Notion OAuth Setup +1. Go to [Notion Developers](https://developers.notion.com/) +2. Create a new integration +3. Configure OAuth settings +4. Note your Client ID and Client Secret + +## Environment Variables + +```bash +# Required for all white-label connectors +VECTORIZE_API_KEY=your_vectorize_api_token +VECTORIZE_ORGANIZATION_ID=your_organization_id + +# Google Drive OAuth credentials +GOOGLE_OAUTH_CLIENT_ID=your_google_client_id +GOOGLE_OAUTH_CLIENT_SECRET=your_google_client_secret +GOOGLE_API_KEY=your_google_api_key + +# Dropbox OAuth credentials +DROPBOX_APP_KEY=your_dropbox_app_key +DROPBOX_APP_SECRET=your_dropbox_app_secret + +# Notion OAuth credentials +NOTION_CLIENT_ID=your_notion_client_id +NOTION_CLIENT_SECRET=your_notion_client_secret +``` + +## Basic Authentication Flow + +### 1. Create White-Label Connector + +```typescript +import { createWhiteLabelGDriveConnector } from '@vectorize-io/vectorize-connect'; + +const vectorizeConfig = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, +}; + +// Create Google Drive connector with your OAuth credentials +const connectorId = await createWhiteLabelGDriveConnector( + vectorizeConfig, + "My Custom Google Drive Connector", + process.env.GOOGLE_OAUTH_CLIENT_ID!, + process.env.GOOGLE_OAUTH_CLIENT_SECRET! +); +``` + +### 2. Start OAuth Flow + +```typescript +import { GoogleDriveOAuth } from '@vectorize-io/vectorize-connect'; + +const oauthConfig = { + clientId: process.env.GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET!, + apiKey: process.env.GOOGLE_API_KEY!, + redirectUri: 'http://localhost:3000/api/oauth/callback', + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: (response) => { + console.log('OAuth successful:', response); + // Handle successful authentication + }, + onError: (error) => { + console.error('OAuth failed:', error); + // Handle authentication error + }, +}; + +// Start OAuth flow +GoogleDriveOAuth.startOAuth(oauthConfig); +``` + +### 3. Handle OAuth Callback + +```typescript +// pages/api/oauth/callback.ts +import { NextApiRequest, NextApiResponse } from 'next'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const { code, error } = req.query; + + if (error) { + return res.status(500).json({ error: 'OAuth authentication failed' }); + } + + if (code) { + try { + // Exchange code for tokens + // Add user to connector + // Redirect to success page + res.redirect('/success'); + } catch (error) { + res.status(500).json({ error: 'Failed to complete authentication' }); + } + } +} +``` + +## Advanced Authentication Patterns + +### Multi-Platform Component + +```typescript +import React, { useState } from 'react'; +import { + GoogleDriveOAuth, + DropboxOAuth, + NotionOAuth +} from '@vectorize-io/vectorize-connect'; + +interface AuthenticationManagerProps { + onSuccess: (platform: string, response: any) => void; + onError: (platform: string, error: Error) => void; +} + +export function AuthenticationManager({ onSuccess, onError }: AuthenticationManagerProps) { + const [isAuthenticating, setIsAuthenticating] = useState(null); + + const startGoogleDriveAuth = () => { + setIsAuthenticating('google-drive'); + GoogleDriveOAuth.startOAuth({ + clientId: process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET!, + apiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY!, + redirectUri: `${window.location.origin}/api/oauth/callback`, + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: (response) => { + setIsAuthenticating(null); + onSuccess('google-drive', response); + }, + onError: (error) => { + setIsAuthenticating(null); + onError('google-drive', error); + }, + }); + }; + + const startDropboxAuth = () => { + setIsAuthenticating('dropbox'); + DropboxOAuth.startOAuth({ + appKey: process.env.NEXT_PUBLIC_DROPBOX_APP_KEY!, + appSecret: process.env.DROPBOX_APP_SECRET!, + redirectUri: `${window.location.origin}/api/dropbox-callback`, + scopes: ['files.metadata.read', 'files.content.read'], + onSuccess: (response) => { + setIsAuthenticating(null); + onSuccess('dropbox', response); + }, + onError: (error) => { + setIsAuthenticating(null); + onError('dropbox', error); + }, + }); + }; + + const startNotionAuth = () => { + setIsAuthenticating('notion'); + NotionOAuth.startOAuth({ + clientId: process.env.NEXT_PUBLIC_NOTION_CLIENT_ID!, + clientSecret: process.env.NOTION_CLIENT_SECRET!, + redirectUri: `${window.location.origin}/api/notion-callback`, + scopes: ['read'], + onSuccess: (response) => { + setIsAuthenticating(null); + onSuccess('notion', response); + }, + onError: (error) => { + setIsAuthenticating(null); + onError('notion', error); + }, + }); + }; + + return ( +
+

Connect Your Accounts

+ + + + + + +
+ ); +} +``` + +### Token Management + +```typescript +import { manageUser } from '@vectorize-io/vectorize-connect'; + +class WhiteLabelTokenManager { + private config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, + }; + + async addUserWithTokens( + connectorId: string, + userId: string, + platform: 'google-drive' | 'dropbox' | 'notion', + tokens: any, + selectedFiles?: string[] + ) { + try { + const result = await manageUser( + this.config, + connectorId, + userId, + 'add', + { + tokens, + selectedFiles, + platform, + } + ); + + return result; + } catch (error) { + console.error(`Failed to add user to ${platform} connector:`, error); + throw error; + } + } + + async refreshUserTokens( + connectorId: string, + userId: string, + refreshToken: string + ) { + // Platform-specific token refresh logic + // This would vary by platform + } +} +``` + +## Error Handling + +```typescript +try { + const connectorId = await createWhiteLabelGDriveConnector( + vectorizeConfig, + "My Connector", + clientId, + clientSecret + ); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API token'); + } else if (error.response?.status === 400) { + console.error('Invalid OAuth credentials provided'); + } else if (error.message.includes('OAuth')) { + console.error('OAuth configuration error:', error.message); + } else { + console.error('Connector creation failed:', error.message); + } +} +``` + +## Security Best Practices + +### Environment Variable Management + +```typescript +// Validate required environment variables +const requiredEnvVars = [ + 'VECTORIZE_API_KEY', + 'VECTORIZE_ORGANIZATION_ID', + 'GOOGLE_OAUTH_CLIENT_ID', + 'GOOGLE_OAUTH_CLIENT_SECRET', +]; + +requiredEnvVars.forEach(envVar => { + if (!process.env[envVar]) { + throw new Error(`Missing required environment variable: ${envVar}`); + } +}); +``` + +### Secure Token Storage + +```typescript +// Never expose client secrets in frontend code +// Use server-side API routes for sensitive operations + +// pages/api/auth/google-drive.ts +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + // Server-side OAuth handling with client secret + const clientSecret = process.env.GOOGLE_OAUTH_CLIENT_SECRET; // Safe on server + + // Process OAuth flow server-side +} +``` + +## Platform-Specific Examples + +For detailed platform-specific authentication examples: + +- [Google Drive Authentication](./google-drive.md) +- [Dropbox Authentication](./dropbox.md) +- [Notion Authentication](./notion.md) + +## Next Steps + +- [User Management](../../user-management/white-label/) +- [Frontend Implementation](../../frontend-implementation/white-label/) diff --git a/docs/authentication/white-label/dropbox.md b/docs/authentication/white-label/dropbox.md new file mode 100644 index 0000000..528b646 --- /dev/null +++ b/docs/authentication/white-label/dropbox.md @@ -0,0 +1,187 @@ +# Dropbox Authentication - White-Label Approach + +Implement Dropbox OAuth authentication using your own credentials. + +## OAuth Callback Route + +Create a file at `app/api/dropbox-callback/route.ts`: + +```typescript +// app/api/dropbox-callback/route.ts +import { NextRequest } from "next/server"; +import { DropboxOAuth } from "@vectorize-io/vectorize-connect"; + +export async function GET(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const code = searchParams.get('code'); + const error = searchParams.get('error'); + + // Configure Dropbox OAuth + const config = { + appKey: process.env.DROPBOX_APP_KEY!, + appSecret: process.env.DROPBOX_APP_SECRET!, + redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/api/dropbox-callback` + }; + + // Create the callback response + return DropboxOAuth.createCallbackResponse( + code || '', + config, + error || undefined + ); + } catch (error: any) { + console.error('Dropbox OAuth callback error:', error); + return new Response(`OAuth Error: ${error.message}`, { status: 500 }); + } +} +``` + +## Frontend OAuth Flow + +```typescript +import { DropboxOAuth } from '@vectorize-io/vectorize-connect'; + +const handleConnectDropbox = async () => { + try { + // Configure Dropbox OAuth + const config = { + appKey: process.env.NEXT_PUBLIC_DROPBOX_APP_KEY!, + appSecret: '', // Client-side should not have the secret + redirectUri: `${window.location.origin}/api/dropbox-callback`, + scopes: ['files.metadata.read', 'files.content.read'], + onSuccess: (selection) => { + console.log('Selected files:', selection.selectedFiles); + console.log('Refresh token:', selection.refreshToken); + + // Handle successful authentication + setSelectedFiles(selection.selectedFiles); + setRefreshToken(selection.refreshToken); + + // Add user to connector + if (connectorId) { + addUserToConnector(connectorId, selection.selectedFiles, selection.refreshToken); + } + }, + onError: (error) => { + console.error('OAuth error:', error); + setError(error.message); + } + }; + + // Start the OAuth flow in a popup + DropboxOAuth.startOAuth(config); + + } catch (error: any) { + console.error('Failed to start OAuth flow:', error); + } +}; +``` + +## Complete Component Example + +```typescript +'use client'; + +import { useState } from 'react'; +import { DropboxOAuth } from '@vectorize-io/vectorize-connect'; + +export default function DropboxConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [selectedFiles, setSelectedFiles] = useState | null>(null); + const [refreshToken, setRefreshToken] = useState(null); + + const handleConnectDropbox = async () => { + setIsLoading(true); + setError(null); + + try { + const config = { + appKey: process.env.NEXT_PUBLIC_DROPBOX_APP_KEY!, + appSecret: '', + redirectUri: `${window.location.origin}/api/dropbox-callback`, + scopes: ['files.metadata.read', 'files.content.read'], + onSuccess: (selection) => { + setSelectedFiles(selection.selectedFiles); + setRefreshToken(selection.refreshToken); + setIsLoading(false); + + if (connectorId) { + addUserToConnector(connectorId, selection.selectedFiles, selection.refreshToken); + } + }, + onError: (error) => { + setError(error.message); + setIsLoading(false); + } + }; + + DropboxOAuth.startOAuth(config); + + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const addUserToConnector = async (connectorId: string, selectedFiles: Record, refreshToken: string) => { + try { + const response = await fetch("/api/manage-dropbox-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + selectedFiles, + refreshToken, + userId: "user123", + action: "add" + }), + }); + + if (!response.ok) { + throw new Error('Failed to add user to connector'); + } + + console.log('User added to connector successfully'); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+

Dropbox White-Label Connection

+ + {error && ( +
+ {error} +
+ )} + + + + {selectedFiles && ( +
+

Selected Files:

+
+            {JSON.stringify(selectedFiles, null, 2)}
+          
+
+ )} +
+ ); +} +``` + +## Next Steps + +- [Dropbox User Management](../../user-management/white-label/dropbox.md) +- [Frontend Implementation](../../frontend-implementation/white-label/) diff --git a/docs/authentication/white-label/google-drive.md b/docs/authentication/white-label/google-drive.md new file mode 100644 index 0000000..c3b73bc --- /dev/null +++ b/docs/authentication/white-label/google-drive.md @@ -0,0 +1,239 @@ +# Google Drive Authentication - White-Label Approach + +Implement Google Drive OAuth authentication using your own credentials. + +## OAuth Callback Route + +Create a file at `app/api/oauth/callback/route.ts`: + +```typescript +// app/api/oauth/callback/route.ts +import { NextRequest } from "next/server"; +import { GoogleDriveOAuth } from "@vectorize-io/vectorize-connect"; + +export async function GET(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const code = searchParams.get('code'); + const error = searchParams.get('error'); + + // Configure Google OAuth + const config = { + clientId: process.env.GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET!, + redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/api/oauth/callback`, + apiKey: process.env.GOOGLE_API_KEY! + }; + + // Create the callback response + return GoogleDriveOAuth.createCallbackResponse( + code || '', + config, + error || undefined + ); + } catch (error: any) { + console.error('Google OAuth callback error:', error); + return new Response(`OAuth Error: ${error.message}`, { status: 500 }); + } +} +``` + +## Frontend OAuth Flow + +```typescript +import { GoogleDriveOAuth, GoogleDriveSelection } from '@vectorize-io/vectorize-connect'; + +const handleConnectGoogleDrive = async () => { + try { + // Configure Google OAuth + const config = { + clientId: process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: '', // Client-side should not have the secret + apiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY!, + redirectUri: `${window.location.origin}/api/oauth/callback`, + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: (response) => { + console.log('Selected files:', response.fileIds); + console.log('Refresh token:', response.refreshToken); + + // Handle successful authentication + setSelectedFiles(response.fileIds); + setRefreshToken(response.refreshToken); + + // Add user to connector + if (connectorId) { + addUserToConnector(connectorId, response.fileIds, response.refreshToken); + } + }, + onError: (error) => { + console.error('OAuth error:', error); + setError(error.message); + } + }; + + // Start the OAuth flow in a popup + GoogleDriveOAuth.startOAuth(config); + + } catch (error: any) { + console.error('Failed to start OAuth flow:', error); + } +}; +``` + +## File Selection with Existing Token + +```typescript +const handleSelectMoreFiles = async () => { + if (!refreshToken) { + setError('No refresh token available. Please connect to Google Drive first.'); + return; + } + + try { + // Configure Google OAuth for file selection + const config = { + clientId: process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: '', // Client-side should not have the secret + apiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY!, + onSuccess: (response) => { + console.log('Additional files selected:', response.fileIds); + setSelectedFiles(response.fileIds); + + // Update user in connector + if (connectorId) { + addUserToConnector(connectorId, response.fileIds, refreshToken); + } + }, + onError: (error) => { + console.error('File selection error:', error); + setError(error.message); + } + }; + + // Start file selection with existing refresh token + await GoogleDriveSelection.startFileSelection(config, refreshToken); + + } catch (error: any) { + console.error('Failed to select files:', error); + } +}; +``` + +## Complete Component Example + +```typescript +'use client'; + +import { useState } from 'react'; +import { GoogleDriveOAuth, GoogleDriveSelection } from '@vectorize-io/vectorize-connect'; + +export default function GoogleDriveConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [selectedFiles, setSelectedFiles] = useState(null); + const [refreshToken, setRefreshToken] = useState(null); + + const handleConnectGoogleDrive = async () => { + setIsLoading(true); + setError(null); + + try { + const config = { + clientId: process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: '', + apiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY!, + redirectUri: `${window.location.origin}/api/oauth/callback`, + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: (response) => { + setSelectedFiles(response.fileIds); + setRefreshToken(response.refreshToken); + setIsLoading(false); + + if (connectorId) { + addUserToConnector(connectorId, response.fileIds, response.refreshToken); + } + }, + onError: (error) => { + setError(error.message); + setIsLoading(false); + } + }; + + GoogleDriveOAuth.startOAuth(config); + + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const addUserToConnector = async (connectorId: string, fileIds: string[], refreshToken: string) => { + try { + const response = await fetch("/api/manage-gdrive-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + fileIds, + refreshToken, + userId: "user123", + action: "add" + }), + }); + + if (!response.ok) { + throw new Error('Failed to add user to connector'); + } + + console.log('User added to connector successfully'); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+

Google Drive White-Label Connection

+ + {error && ( +
+ {error} +
+ )} + + + + {refreshToken && ( + + )} + + {selectedFiles && ( +
+

Selected Files:

+
+            {JSON.stringify(selectedFiles, null, 2)}
+          
+
+ )} +
+ ); +} +``` + +## Next Steps + +- [Google Drive User Management](../../user-management/white-label/google-drive.md) +- [Frontend Implementation](../../frontend-implementation/white-label/) diff --git a/docs/authentication/white-label/notion.md b/docs/authentication/white-label/notion.md new file mode 100644 index 0000000..34ec93d --- /dev/null +++ b/docs/authentication/white-label/notion.md @@ -0,0 +1,278 @@ +# Notion Authentication - White-Label Approach + +Implement Notion OAuth authentication using your own credentials. + +## Prerequisites + +Before implementing Notion authentication, you need: + +1. **Notion Integration**: Create a Notion integration in your [Notion Developer Portal](https://www.notion.so/my-integrations) +2. **OAuth Credentials**: Obtain your Client ID and Client Secret from the integration settings +3. **Redirect URI**: Configure the redirect URI in your Notion integration settings + +## Environment Setup + +```bash +# Required environment variables +VECTORIZE_API_KEY=your_vectorize_api_key +VECTORIZE_ORGANIZATION_ID=your_organization_id + +# Notion OAuth credentials +NOTION_CLIENT_ID=your_notion_client_id +NOTION_CLIENT_SECRET=your_notion_client_secret +``` + +## OAuth Callback Route + +Create a file at `app/api/notion-callback/route.ts`: + +```typescript +// app/api/notion-callback/route.ts +import { NextRequest } from "next/server"; +import { NotionOAuth } from "@vectorize-io/vectorize-connect"; + +export async function GET(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const code = searchParams.get('code'); + const error = searchParams.get('error'); + + // Configure Notion OAuth + const config = { + clientId: process.env.NOTION_CLIENT_ID!, + clientSecret: process.env.NOTION_CLIENT_SECRET!, + redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/api/notion-callback`, + scopes: ['read_content'] + }; + + // Create the callback response + return NotionOAuth.createCallbackResponse( + code || '', + config, + error || undefined + ); + } catch (error: any) { + console.error('Notion OAuth callback error:', error); + return new Response(`OAuth Error: ${error.message}`, { status: 500 }); + } +} +``` + +## Frontend OAuth Flow + +```typescript +import { NotionOAuth } from '@vectorize-io/vectorize-connect'; + +const handleConnectNotion = async () => { + try { + // Configure Notion OAuth + const config = { + clientId: process.env.NEXT_PUBLIC_NOTION_CLIENT_ID!, + clientSecret: '', // Client-side should not have the secret + redirectUri: `${window.location.origin}/api/notion-callback`, + scopes: ['read_content'], + onSuccess: (selection) => { + console.log('Selected pages:', selection.pages); + console.log('Access token:', selection.accessToken); + console.log('Workspace info:', selection.workspaceId, selection.workspaceName); + + // Handle successful authentication + setSelectedPages(selection.pages); + setAccessToken(selection.accessToken); + + // Add user to connector + if (connectorId) { + addUserToConnector(connectorId, selection.pages, selection.accessToken); + } + }, + onError: (error) => { + console.error('OAuth error:', error); + setError(error.message); + } + }; + + // Start the OAuth flow in a popup + NotionOAuth.startOAuth(config); + + } catch (error: any) { + console.error('Failed to start OAuth flow:', error); + } +}; +``` + +## Complete Component Example + +```typescript +'use client'; + +import { useState } from 'react'; +import { NotionOAuth } from '@vectorize-io/vectorize-connect'; + +export default function NotionConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [selectedPages, setSelectedPages] = useState(null); + const [accessToken, setAccessToken] = useState(null); + + const handleConnectNotion = async () => { + setIsLoading(true); + setError(null); + + try { + const config = { + clientId: process.env.NEXT_PUBLIC_NOTION_CLIENT_ID!, + clientSecret: '', + redirectUri: `${window.location.origin}/api/notion-callback`, + scopes: ['read_content'], + onSuccess: (selection) => { + setSelectedPages(selection.pages); + setAccessToken(selection.accessToken); + setIsLoading(false); + + if (connectorId) { + addUserToConnector(connectorId, selection.pages, selection.accessToken); + } + }, + onError: (error) => { + setError(error.message); + setIsLoading(false); + } + }; + + NotionOAuth.startOAuth(config); + + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const addUserToConnector = async (connectorId: string, pages: any[], accessToken: string) => { + try { + const response = await fetch("/api/manage-notion-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + selectedPages: pages.reduce((acc, page) => { + acc[page.id] = { + title: page.title, + pageId: page.id, + parentType: page.parentType + }; + return acc; + }, {}), + accessToken, + userId: "user123", + action: "add" + }), + }); + + if (!response.ok) { + throw new Error('Failed to add user to connector'); + } + + console.log('User added to connector successfully'); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+

Notion White-Label Connection

+ + {error && ( +
+ {error} +
+ )} + + + + {selectedPages && ( +
+

Selected Pages:

+
+            {JSON.stringify(selectedPages, null, 2)}
+          
+
+ )} +
+ ); +} +``` + +## Token Management + +### Exchange Authorization Code + +```typescript +import { exchangeNotionCodeForTokens } from '@vectorize-io/vectorize-connect'; + +const tokens = await exchangeNotionCodeForTokens( + authorizationCode, + process.env.NOTION_CLIENT_ID!, + process.env.NOTION_CLIENT_SECRET!, + redirectUri +); + +console.log('Access token:', tokens.access_token); +console.log('Workspace:', tokens.workspace_name); +console.log('Bot ID:', tokens.bot_id); +``` + +### Validate Access Token + +```typescript +import { refreshNotionToken } from '@vectorize-io/vectorize-connect'; + +const validatedToken = await refreshNotionToken( + accessToken, + process.env.NOTION_CLIENT_ID!, + process.env.NOTION_CLIENT_SECRET! +); + +console.log('Token is valid for workspace:', validatedToken.workspace_name); +``` + +## Error Handling + +```typescript +try { + const config = { + clientId: process.env.NEXT_PUBLIC_NOTION_CLIENT_ID!, + clientSecret: '', + redirectUri: `${window.location.origin}/api/notion-callback`, + scopes: ['read_content'], + onSuccess: (selection) => { + // Handle success + }, + onError: (error) => { + if (error.code === 'CONFIGURATION_ERROR') { + console.error('OAuth configuration error:', error.message); + } else if (error.code === 'TOKEN_EXCHANGE_ERROR') { + console.error('Failed to exchange code for tokens:', error.message); + } else { + console.error('OAuth error:', error.message); + } + } + }; + + NotionOAuth.startOAuth(config); +} catch (error: any) { + console.error('Failed to start OAuth flow:', error); +} +``` + +## Next Steps + +- [Notion User Management](../../user-management/white-label/notion.md) +- [Frontend Implementation](../../frontend-implementation/white-label/) diff --git a/docs/creating-connectors/README.md b/docs/creating-connectors/README.md new file mode 100644 index 0000000..dd48959 --- /dev/null +++ b/docs/creating-connectors/README.md @@ -0,0 +1,78 @@ +# Creating Connectors + +This section covers how to create connectors using different approaches. + +## Approaches + +- **[Vectorize](./vectorize/)** - Create connectors using Vectorize's managed OAuth credentials +- **[White-Label](./white-label/)** - Create connectors using your own OAuth credentials + +## Overview + +Connectors are the foundation of your integration. They define how users will authenticate and which files they can access from their cloud storage accounts. + +### Vectorize Approach +- Uses Vectorize's pre-configured OAuth applications +- Simpler setup with fewer credentials to manage +- Consistent experience across all platforms + +### White-Label Approach +- Uses your own OAuth applications +- Full control over branding and user experience +- Requires setting up OAuth apps for each platform + +## Quick Reference + +### Vectorize Connector Creation +```typescript +// For Google Drive +import { createVectorizeGDriveConnector } from '@vectorize-io/vectorize-connect'; +const gdriveConnectorId = await createVectorizeGDriveConnector( + vectorizeConfig, + "My Google Drive Connector" +); + +// For Dropbox +import { createVectorizeDropboxConnector } from '@vectorize-io/vectorize-connect'; +const dropboxConnectorId = await createVectorizeDropboxConnector( + vectorizeConfig, + "My Dropbox Connector" +); + +// For Notion +import { createVectorizeNotionConnector } from '@vectorize-io/vectorize-connect'; +const notionConnectorId = await createVectorizeNotionConnector( + vectorizeConfig, + "My Notion Connector" +); +``` + +### White-Label Connector Creation +```typescript +// For Google Drive +import { createWhiteLabelGDriveConnector } from '@vectorize-io/vectorize-connect'; +const gdriveConnectorId = await createWhiteLabelGDriveConnector( + vectorizeConfig, + "My Custom Google Drive Connector", + googleClientId, + googleClientSecret +); + +// For Dropbox +import { createWhiteLabelDropboxConnector } from '@vectorize-io/vectorize-connect'; +const dropboxConnectorId = await createWhiteLabelDropboxConnector( + vectorizeConfig, + "My Custom Dropbox Connector", + dropboxAppKey, + dropboxAppSecret +); + +// For Notion +import { createWhiteLabelNotionConnector } from '@vectorize-io/vectorize-connect'; +const notionConnectorId = await createWhiteLabelNotionConnector( + vectorizeConfig, + "My Custom Notion Connector", + notionClientId, + notionClientSecret +); +``` diff --git a/docs/creating-connectors/vectorize/README.md b/docs/creating-connectors/vectorize/README.md new file mode 100644 index 0000000..fcf07cc --- /dev/null +++ b/docs/creating-connectors/vectorize/README.md @@ -0,0 +1,430 @@ +# Creating Connectors - Vectorize Approach + +Create connectors using Vectorize's managed OAuth credentials for quick setup. + +## Supported Connectors + +Vectorize provides managed OAuth credentials for the following platforms: +- **Google Drive** (`GOOGLE_DRIVE_OAUTH_MULTI`) +- **Dropbox** (`DROPBOX_OAUTH_MULTI`) +- **Notion** (`NOTION_OAUTH_MULTI`) + +## API Route Implementation + +Create a file at `app/api/createConnector/route.ts`: + +```typescript +// app/api/createConnector/route.ts +import { NextResponse } from "next/server"; +import { + createVectorizeGDriveConnector, + createVectorizeDropboxConnector, + createVectorizeNotionConnector +} from "@vectorize-io/vectorize-connect"; + +interface VectorizeAPIConfig { + organizationId: string; + authorization: string; +} + +export async function POST(request: Request) { + try { + // Parse the incoming request + const { connectorName, platformType } = await request.json(); + + // Gather environment variables for your Vectorize config + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", + }; + + // Validate environment variables + if (!config.organizationId || !config.authorization) { + return NextResponse.json( + { error: "Missing Vectorize credentials in environment" }, + { status: 500 } + ); + } + + // Create the connector based on platform type + let connectorId: string; + + switch (platformType) { + case "GOOGLE_DRIVE_OAUTH_MULTI": + connectorId = await createVectorizeGDriveConnector(config, connectorName); + break; + case "DROPBOX_OAUTH_MULTI": + connectorId = await createVectorizeDropboxConnector(config, connectorName); + break; + case "NOTION_OAUTH_MULTI": + connectorId = await createVectorizeNotionConnector(config, connectorName); + break; + default: + return NextResponse.json( + { error: `Unsupported platform type: ${platformType}` }, + { status: 400 } + ); + } + + return NextResponse.json({ connectorId }, { status: 200 }); + } catch (error: any) { + return NextResponse.json({ error: error.message || "Unexpected error" }, { status: 500 }); + } +} +``` + +## Connector Examples + +### Google Drive Multi-User Connector + +```typescript +import { createVectorizeGDriveConnector } from "@vectorize-io/vectorize-connect"; + +const config = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, + authorization: process.env.VECTORIZE_API_KEY!, +}; + +// Create Google Drive connector +const gdriveConnectorId = await createVectorizeGDriveConnector( + config, + "Team Google Drive" +); + +console.log('Google Drive connector created:', gdriveConnectorId); +``` + +### Dropbox Multi-User Connector + +```typescript +import { createVectorizeDropboxConnector } from "@vectorize-io/vectorize-connect"; + +const config = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, + authorization: process.env.VECTORIZE_API_KEY!, +}; + +// Create Dropbox connector +const dropboxConnectorId = await createVectorizeDropboxConnector( + config, + "Team Dropbox Storage" +); + +console.log('Dropbox connector created:', dropboxConnectorId); +``` + +### Notion Multi-User Connector + +```typescript +import { createVectorizeNotionConnector } from "@vectorize-io/vectorize-connect"; + +const config = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, + authorization: process.env.VECTORIZE_API_KEY!, +}; + +// Create Notion connector +const notionConnectorId = await createVectorizeNotionConnector( + config, + "Team Notion Workspace" +); + +console.log('Notion connector created:', notionConnectorId); +``` + +## Alternative: Using createSourceConnector + +For more control over connector configuration, you can use the generic `createSourceConnector` function for any platform: + +```typescript +import { createSourceConnector } from '@vectorize-io/vectorize-connect'; + +// Google Drive with createSourceConnector +const gdriveConnectorId = await createSourceConnector( + config, + { + name: "Team Google Drive", + type: "GOOGLE_DRIVE_OAUTH_MULTI", + config: {} + } +); + +// Dropbox with createSourceConnector +const dropboxConnectorId = await createSourceConnector( + config, + { + name: "Team Dropbox Storage", + type: "DROPBOX_OAUTH_MULTI", + config: {} + } +); + +// Notion with createVectorizeNotionConnector +const notionConnectorId = await createVectorizeNotionConnector( + config, + "Team Notion Workspace" +); +``` + +## Frontend Usage Examples + +### Google Drive Connector Creation + +```typescript +const handleCreateGoogleDriveConnector = async () => { + try { + const response = await fetch("/api/createConnector", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + connectorName: "Team Google Drive", + platformType: "GOOGLE_DRIVE_OAUTH_MULTI" + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create Google Drive connector'); + } + + const { connectorId } = await response.json(); + console.log('Google Drive connector created:', connectorId); + } catch (error: any) { + console.error('Error creating Google Drive connector:', error.message); + } +}; +``` + +### Dropbox Connector Creation + +```typescript +const handleCreateDropboxConnector = async () => { + try { + const response = await fetch("/api/createConnector", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + connectorName: "Team Dropbox Storage", + platformType: "DROPBOX_OAUTH_MULTI" + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create Dropbox connector'); + } + + const { connectorId } = await response.json(); + console.log('Dropbox connector created:', connectorId); + } catch (error: any) { + console.error('Error creating Dropbox connector:', error.message); + } +}; +``` + +### Notion Connector Creation + +```typescript +const handleCreateNotionConnector = async () => { + try { + const response = await fetch("/api/createConnector", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + connectorName: "Team Notion Workspace", + platformType: "NOTION_OAUTH_MULTI" + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create Notion connector'); + } + + const { connectorId } = await response.json(); + console.log('Notion connector created:', connectorId); + } catch (error: any) { + console.error('Error creating Notion connector:', error.message); + } +}; +``` + +### Dynamic Connector Creation + +```typescript +const handleCreateConnector = async (platformType: string, connectorName: string) => { + try { + const response = await fetch("/api/createConnector", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + connectorName, + platformType + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create connector'); + } + + const { connectorId } = await response.json(); + console.log(`${platformType} connector created:`, connectorId); + return connectorId; + } catch (error: any) { + console.error(`Error creating ${platformType} connector:`, error.message); + throw error; + } +}; + +// Usage examples +await handleCreateConnector("GOOGLE_DRIVE_OAUTH_MULTI", "Team Google Drive"); +await handleCreateConnector("DROPBOX_OAUTH_MULTI", "Team Dropbox Storage"); +await handleCreateConnector("NOTION_OAUTH_MULTI", "Team Notion Workspace"); +``` + +## Error Handling + +### Basic Error Handling + +```typescript +// Google Drive error handling +try { + const connectorId = await createVectorizeGDriveConnector( + vectorizeConfig, + "Team Google Drive" + ); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API token'); + } else if (error.response?.status === 403) { + console.error('Insufficient permissions or plan limitations'); + } else { + console.error('Google Drive connector creation failed:', error.message); + } +} + +// Dropbox error handling +try { + const connectorId = await createVectorizeDropboxConnector( + vectorizeConfig, + "Team Dropbox Storage" + ); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API token'); + } else if (error.response?.status === 403) { + console.error('Insufficient permissions or plan limitations'); + } else { + console.error('Dropbox connector creation failed:', error.message); + } +} + +// Notion error handling +try { + const connectorId = await createVectorizeNotionConnector( + vectorizeConfig, + "Team Notion Workspace" + ); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API token'); + } else if (error.response?.status === 403) { + console.error('Insufficient permissions or plan limitations'); + } else { + console.error('Notion connector creation failed:', error.message); + } +} +``` + +### Platform-Specific Error Handling + +```typescript +const createGoogleDriveConnector = async (connectorName: string) => { + try { + const connectorId = await createVectorizeGDriveConnector( + vectorizeConfig, + connectorName + ); + + console.log('Google Drive connector created successfully:', connectorId); + return connectorId; + } catch (error) { + if (error.response?.status === 401) { + throw new Error('Invalid Vectorize API credentials for Google Drive'); + } else if (error.response?.status === 403) { + throw new Error('Insufficient permissions for Google Drive connector creation'); + } else if (error.response?.status === 400) { + throw new Error('Invalid configuration for Google Drive connector'); + } else { + throw new Error(`Failed to create Google Drive connector: ${error.message}`); + } + } +}; + +const createDropboxConnector = async (connectorName: string) => { + try { + const connectorId = await createVectorizeDropboxConnector( + vectorizeConfig, + connectorName + ); + + console.log('Dropbox connector created successfully:', connectorId); + return connectorId; + } catch (error) { + if (error.response?.status === 401) { + throw new Error('Invalid Vectorize API credentials for Dropbox'); + } else if (error.response?.status === 403) { + throw new Error('Insufficient permissions for Dropbox connector creation'); + } else if (error.response?.status === 400) { + throw new Error('Invalid configuration for Dropbox connector'); + } else { + throw new Error(`Failed to create Dropbox connector: ${error.message}`); + } + } +}; + +const createNotionConnector = async (connectorName: string) => { + try { + const connectorId = await createVectorizeNotionConnector( + vectorizeConfig, + connectorName + ); + + console.log('Notion connector created successfully:', connectorId); + return connectorId; + } catch (error) { + if (error.response?.status === 401) { + throw new Error('Invalid Vectorize API credentials for Notion'); + } else if (error.response?.status === 403) { + throw new Error('Insufficient permissions for Notion connector creation'); + } else if (error.response?.status === 400) { + throw new Error('Invalid configuration for Notion connector'); + } else { + throw new Error(`Failed to create Notion connector: ${error.message}`); + } + } +}; + +// Usage with error handling +try { + await createGoogleDriveConnector("Team Google Drive"); + await createDropboxConnector("Team Dropbox Storage"); + await createNotionConnector("Team Notion Workspace"); +} catch (error) { + console.error('Connector creation failed:', error.message); +} +``` + +## Next Steps + +- [Authentication](../../authentication/vectorize/) +- [User Management](../../user-management/vectorize/) diff --git a/docs/creating-connectors/white-label/README.md b/docs/creating-connectors/white-label/README.md new file mode 100644 index 0000000..6b9f7a9 --- /dev/null +++ b/docs/creating-connectors/white-label/README.md @@ -0,0 +1,177 @@ +# Creating Connectors - White-Label Approach + +Create connectors using your own OAuth credentials for full control over the user experience. + +## Overview + +White-label connectors allow you to use your own OAuth applications, providing: + +- **Custom Branding**: Users stay within your application's branding +- **Full Control**: Complete control over the OAuth flow and user experience +- **Direct Integration**: No redirection to external platforms +- **Flexibility**: Ability to customize the authentication and file selection experience + +## Platform-Specific Guides + +- [Google Drive Connectors](./google-drive.md) +- [Dropbox Connectors](./dropbox.md) + +## General Implementation Pattern + +All white-label connectors follow a similar pattern: + +### 1. Environment Setup +Configure both Vectorize and platform-specific credentials: + +```env +# Vectorize credentials +VECTORIZE_API_KEY=your_vectorize_api_token +VECTORIZE_ORGANIZATION_ID=your_organization_id + +# Platform-specific OAuth credentials +PLATFORM_OAUTH_CLIENT_ID=your_client_id +PLATFORM_OAUTH_CLIENT_SECRET=your_client_secret +``` + +### 2. Connector Creation API +Create an API route for connector creation: + +```typescript +// app/api/createPlatformConnector/route.ts +import { NextResponse } from "next/server"; +import { createWhiteLabelPlatformConnector } from "@vectorize-io/vectorize-connect"; + +export async function POST(request: Request) { + try { + const { connectorName } = await request.json(); + + const config = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", + }; + + const connectorId = await createWhiteLabelPlatformConnector( + config, + connectorName, + process.env.PLATFORM_OAUTH_CLIENT_ID!, + process.env.PLATFORM_OAUTH_CLIENT_SECRET! + ); + + return NextResponse.json({ connectorId }, { status: 200 }); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} +``` + +### 3. OAuth Callback Handling +Implement OAuth callback routes for each platform: + +```typescript +// app/api/platform-callback/route.ts +import { NextRequest } from "next/server"; +import { PlatformOAuth } from "@vectorize-io/vectorize-connect"; + +export async function GET(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const code = searchParams.get('code'); + const error = searchParams.get('error'); + + const config = { + clientId: process.env.PLATFORM_OAUTH_CLIENT_ID!, + clientSecret: process.env.PLATFORM_OAUTH_CLIENT_SECRET!, + redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/api/platform-callback` + }; + + return PlatformOAuth.createCallbackResponse( + code || '', + config, + error || undefined + ); + } catch (error: any) { + return new Response(`OAuth Error: ${error.message}`, { status: 500 }); + } +} +``` + +### 4. Frontend Implementation +Create components that handle the OAuth flow: + +```typescript +'use client'; + +import { useState } from 'react'; +import { PlatformOAuth } from '@vectorize-io/vectorize-connect'; + +export default function PlatformConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const handleConnect = async () => { + setIsLoading(true); + + try { + const config = { + clientId: process.env.NEXT_PUBLIC_PLATFORM_OAUTH_CLIENT_ID!, + redirectUri: `${window.location.origin}/api/platform-callback`, + onSuccess: (response) => { + // Handle successful authentication + console.log('Authentication successful:', response); + setIsLoading(false); + }, + onError: (error) => { + console.error('Authentication failed:', error); + setIsLoading(false); + } + }; + + PlatformOAuth.startOAuth(config); + + } catch (error: any) { + console.error('Failed to start OAuth:', error); + setIsLoading(false); + } + }; + + return ( +
+ +
+ ); +} +``` + +## Advantages + +1. **Brand Consistency**: Users never leave your application +2. **Customization**: Full control over UI/UX and authentication flow +3. **Security**: Direct control over OAuth credentials and token management +4. **Compliance**: Easier to meet specific security and compliance requirements + +## Considerations + +1. **Setup Complexity**: Requires setting up OAuth applications for each platform +2. **Maintenance**: Need to maintain OAuth credentials and handle updates +3. **Support**: Responsible for troubleshooting OAuth-related issues + +## Platform-Specific Guides + +For detailed implementation guides for specific platforms: + +- [Google Drive White-Label Guide](./google-drive.md) +- [Dropbox White-Label Guide](./dropbox.md) +- [Notion White-Label Guide](./notion.md) + +## Next Steps + +Choose your platform and follow the specific implementation guide above, or continue with: + +- [Authentication](../../authentication/white-label/) +- [User Management](../../user-management/white-label/) diff --git a/docs/creating-connectors/white-label/dropbox.md b/docs/creating-connectors/white-label/dropbox.md new file mode 100644 index 0000000..3133244 --- /dev/null +++ b/docs/creating-connectors/white-label/dropbox.md @@ -0,0 +1,100 @@ +# Creating Dropbox Connectors - White-Label Approach + +Create Dropbox connectors using your own OAuth credentials. + +## Prerequisites + +- Dropbox App credentials (App Key, App Secret) +- Configured redirect URI in Dropbox Developer Console + +## API Route Implementation + +Create a file at `app/api/createDropboxConnector/route.ts`: + +```typescript +// app/api/createDropboxConnector/route.ts +import { NextResponse } from "next/server"; +import { createWhiteLabelDropboxConnector } from "@vectorize-io/vectorize-connect"; + +interface VectorizeAPIConfig { + organizationId: string; + authorization: string; +} + +export async function POST(request: Request) { + try { + // Parse the incoming request + const { connectorName } = await request.json(); + + // Gather environment variables for your Vectorize config + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", + }; + + // Validate environment variables + if (!config.organizationId || !config.authorization) { + return NextResponse.json( + { error: "Missing Vectorize credentials in environment" }, + { status: 500 } + ); + } + + // Validate Dropbox credentials + const appKey = process.env.DROPBOX_APP_KEY; + const appSecret = process.env.DROPBOX_APP_SECRET; + + if (!appKey || !appSecret) { + return NextResponse.json( + { error: "Missing Dropbox credentials in environment" }, + { status: 500 } + ); + } + + // Create the connector (White-Label) + const connectorId = await createWhiteLabelDropboxConnector( + config, + connectorName, + appKey, + appSecret + ); + + return NextResponse.json({ connectorId }, { status: 200 }); + } catch (error: any) { + return NextResponse.json({ error: error.message || "Unexpected error" }, { status: 500 }); + } +} +``` + +## Frontend Usage + +```typescript +const handleCreateConnector = async () => { + try { + const response = await fetch("/api/createDropboxConnector", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + connectorName: "My Custom Dropbox Connector", + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create connector'); + } + + const { connectorId } = await response.json(); + console.log('Dropbox connector created:', connectorId); + } catch (error: any) { + console.error('Error creating connector:', error.message); + } +}; +``` + +## Next Steps + +- [Dropbox Authentication](../../authentication/white-label/dropbox.md) +- [Dropbox User Management](../../user-management/white-label/dropbox.md) diff --git a/docs/creating-connectors/white-label/google-drive.md b/docs/creating-connectors/white-label/google-drive.md new file mode 100644 index 0000000..e3826e7 --- /dev/null +++ b/docs/creating-connectors/white-label/google-drive.md @@ -0,0 +1,100 @@ +# Creating Google Drive Connectors - White-Label Approach + +Create Google Drive connectors using your own OAuth credentials. + +## Prerequisites + +- Google OAuth credentials (Client ID, Client Secret, API Key) +- Configured redirect URI in Google Cloud Console + +## API Route Implementation + +Create a file at `app/api/createGDriveConnector/route.ts`: + +```typescript +// app/api/createGDriveConnector/route.ts +import { NextResponse } from "next/server"; +import { createWhiteLabelGDriveConnector } from "@vectorize-io/vectorize-connect"; + +interface VectorizeAPIConfig { + organizationId: string; + authorization: string; +} + +export async function POST(request: Request) { + try { + // Parse the incoming request + const { connectorName } = await request.json(); + + // Gather environment variables for your Vectorize config + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", + }; + + // Validate environment variables + if (!config.organizationId || !config.authorization) { + return NextResponse.json( + { error: "Missing Vectorize credentials in environment" }, + { status: 500 } + ); + } + + // Validate Google OAuth credentials + const clientId = process.env.GOOGLE_OAUTH_CLIENT_ID; + const clientSecret = process.env.GOOGLE_OAUTH_CLIENT_SECRET; + + if (!clientId || !clientSecret) { + return NextResponse.json( + { error: "Missing Google OAuth credentials in environment" }, + { status: 500 } + ); + } + + // Create the connector (White-Label) + const connectorId = await createWhiteLabelGDriveConnector( + config, + connectorName, + clientId, + clientSecret + ); + + return NextResponse.json({ connectorId }, { status: 200 }); + } catch (error: any) { + return NextResponse.json({ error: error.message || "Unexpected error" }, { status: 500 }); + } +} +``` + +## Frontend Usage + +```typescript +const handleCreateConnector = async () => { + try { + const response = await fetch("/api/createGDriveConnector", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + connectorName: "My Custom Google Drive Connector", + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create connector'); + } + + const { connectorId } = await response.json(); + console.log('Google Drive connector created:', connectorId); + } catch (error: any) { + console.error('Error creating connector:', error.message); + } +}; +``` + +## Next Steps + +- [Google Drive Authentication](../../authentication/white-label/google-drive.md) +- [Google Drive User Management](../../user-management/white-label/google-drive.md) diff --git a/docs/creating-connectors/white-label/notion.md b/docs/creating-connectors/white-label/notion.md new file mode 100644 index 0000000..bf73241 --- /dev/null +++ b/docs/creating-connectors/white-label/notion.md @@ -0,0 +1,307 @@ +# Creating White-Label Notion Connectors + +This guide covers creating Notion connectors using your own OAuth credentials for complete control over branding and user experience. + +## Prerequisites + +Before creating a White-Label Notion connector, you need: + +1. **Notion Integration**: Create a Notion integration in your [Notion Developer Portal](https://www.notion.so/my-integrations) +2. **OAuth Credentials**: Obtain your Client ID and Client Secret from the integration settings +3. **Redirect URI**: Configure the redirect URI in your Notion integration settings + +## Environment Setup + +```bash +# Required environment variables +VECTORIZE_API_KEY=your_vectorize_api_key +VECTORIZE_ORGANIZATION_ID=your_organization_id + +# Notion OAuth credentials +NOTION_CLIENT_ID=your_notion_client_id +NOTION_CLIENT_SECRET=your_notion_client_secret +``` + +## Creating the Connector + +### Using the SDK + +```typescript +import { createWhiteLabelNotionConnector } from '@vectorize-io/vectorize-connect'; + +const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, +}; + +const connectorId = await createWhiteLabelNotionConnector( + config, + "My Custom Notion Connector", + process.env.NOTION_CLIENT_ID!, + process.env.NOTION_CLIENT_SECRET! +); +``` + +### API Route Implementation + +```typescript +// app/api/create-notion-connector/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { createWhiteLabelNotionConnector } from '@vectorize-io/vectorize-connect'; + +export async function POST(request: NextRequest) { + try { + const { connectorName } = await request.json(); + + const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, + }; + + const connectorId = await createWhiteLabelNotionConnector( + config, + connectorName, + process.env.NOTION_CLIENT_ID!, + process.env.NOTION_CLIENT_SECRET! + ); + + return NextResponse.json({ connectorId }); + } catch (error) { + console.error('Error creating Notion connector:', error); + return NextResponse.json( + { error: 'Failed to create connector' }, + { status: 500 } + ); + } +} +``` + +## User Management + +### Adding Users + +```typescript +import { manageNotionUser } from '@vectorize-io/vectorize-connect'; + +const selectedPages = { + "page_id_1": { + title: "Project Documentation", + pageId: "page_id_1", + parentType: "workspace" + }, + "page_id_2": { + title: "Meeting Notes", + pageId: "page_id_2", + parentType: "database" + } +}; + +const response = await manageNotionUser( + config, + connectorId, + selectedPages, + notionAccessToken, + userId, + "add" +); +``` + +### Editing User Access + +```typescript +const updatedPages = { + "page_id_1": { + title: "Updated Project Documentation", + pageId: "page_id_1", + parentType: "workspace" + } +}; + +const response = await manageNotionUser( + config, + connectorId, + updatedPages, + notionAccessToken, + userId, + "edit" +); +``` + +### Removing Users + +```typescript +const response = await manageNotionUser( + config, + connectorId, + null, // No pages needed for removal + "", // No access token needed for removal + userId, + "remove" +); +``` + +## OAuth Flow Implementation + +### Frontend Component + +```typescript +import { NotionOAuth } from '@vectorize-io/vectorize-connect'; + +function NotionConnector() { + const handleConnect = async () => { + const notionOAuth = new NotionOAuth({ + clientId: process.env.NEXT_PUBLIC_NOTION_CLIENT_ID!, + clientSecret: '', // Keep empty on frontend + redirectUri: `${window.location.origin}/callback/notion`, + scopes: ['read_content'] + }); + + // Get one-time token for authentication + const tokenResponse = await fetch('/api/get-one-time-connector-token', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId: 'user123', + connectorId: connectorId + }) + }); + + const { token } = await tokenResponse.json(); + + // Redirect to Notion OAuth + notionOAuth.redirectToVectorizeConnect(token); + }; + + return ( + + ); +} +``` + +### OAuth Callback Handler + +```typescript +// app/callback/notion/page.tsx +'use client'; + +import { useEffect } from 'react'; +import { useSearchParams } from 'next/navigation'; + +export default function NotionCallback() { + const searchParams = useSearchParams(); + + useEffect(() => { + const code = searchParams.get('code'); + const error = searchParams.get('error'); + + if (error) { + console.error('OAuth error:', error); + return; + } + + if (code) { + // Handle successful OAuth callback + handleOAuthSuccess(code); + } + }, [searchParams]); + + const handleOAuthSuccess = async (code: string) => { + try { + // Exchange code for tokens and complete user setup + const response = await fetch('/api/complete-notion-oauth', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ code }) + }); + + if (response.ok) { + // Redirect to success page or close popup + window.location.href = '/success'; + } + } catch (error) { + console.error('Error completing OAuth:', error); + } + }; + + return
Processing Notion connection...
; +} +``` + +## Token Management + +### Exchange Authorization Code + +```typescript +import { exchangeNotionCodeForTokens } from '@vectorize-io/vectorize-connect'; + +const tokens = await exchangeNotionCodeForTokens( + authorizationCode, + process.env.NOTION_CLIENT_ID!, + process.env.NOTION_CLIENT_SECRET!, + redirectUri +); +``` + +### Validate Access Token + +```typescript +import { refreshNotionToken } from '@vectorize-io/vectorize-connect'; + +const validatedToken = await refreshNotionToken(accessToken); +``` + +## Error Handling + +### Common Error Scenarios + +```typescript +try { + const connectorId = await createWhiteLabelNotionConnector( + config, + connectorName, + clientId, + clientSecret + ); +} catch (error) { + if (error.message.includes('Client ID and Client Secret are required')) { + // Handle missing credentials + console.error('OAuth credentials not configured'); + } else if (error.message.includes('500 Internal Server Error')) { + // Handle API errors + console.error('Server error creating connector'); + } else { + // Handle other errors + console.error('Unexpected error:', error); + } +} +``` + +### User Management Errors + +```typescript +try { + await manageNotionUser(config, connectorId, selectedPages, accessToken, userId, "add"); +} catch (error) { + if (error.message.includes('Selected pages are required')) { + // Handle missing page selection + } else if (error.message.includes('Access token is required')) { + // Handle missing access token + } +} +``` + +## Best Practices + +1. **Secure Credential Storage**: Store OAuth credentials securely using environment variables +2. **Error Handling**: Implement comprehensive error handling for OAuth flows +3. **Token Validation**: Regularly validate access tokens before making API calls +4. **User Experience**: Provide clear feedback during the OAuth process +5. **Page Selection**: Allow users to select specific Notion pages they want to sync + +## Next Steps + +- [User Management Guide](../../user-management/white-label/) +- [Frontend Implementation](../../frontend-implementation/white-label/) +- [Testing Guide](../../testing/white-label/) diff --git a/docs/environment-setup/README.md b/docs/environment-setup/README.md new file mode 100644 index 0000000..37e6755 --- /dev/null +++ b/docs/environment-setup/README.md @@ -0,0 +1,28 @@ +# Environment Setup + +This section covers how to configure environment variables for different connector approaches. + +## Approaches + +- **[Vectorize](./vectorize/)** - Using Vectorize's managed OAuth credentials +- **[White-Label](./white-label/)** - Using your own OAuth credentials + +## Quick Reference + +### Vectorize Approach +```env +VECTORIZE_API_KEY=your_vectorize_api_token +VECTORIZE_ORGANIZATION_ID=your_organization_id +``` + +### White-Label Approach +```env +# Vectorize credentials +VECTORIZE_API_KEY=your_vectorize_api_token +VECTORIZE_ORGANIZATION_ID=your_organization_id + +# Platform-specific OAuth credentials +PLATFORM_OAUTH_CLIENT_ID=your_client_id +PLATFORM_OAUTH_CLIENT_SECRET=your_client_secret +PLATFORM_API_KEY=your_api_key # If required by platform +``` diff --git a/docs/environment-setup/vectorize/README.md b/docs/environment-setup/vectorize/README.md new file mode 100644 index 0000000..499a6fa --- /dev/null +++ b/docs/environment-setup/vectorize/README.md @@ -0,0 +1,40 @@ +# Environment Setup - Vectorize Approach + +When using Vectorize's managed OAuth credentials, you only need to configure your Vectorize API credentials. + +## Required Environment Variables + +Add the following environment variables to your Next.js application: + +```env +# Vectorize credentials +VECTORIZE_API_KEY=your_vectorize_api_token +VECTORIZE_ORGANIZATION_ID=your_organization_id +``` + +## Getting Your Credentials + +1. Log in to your Vectorize account +2. Navigate to your organization settings +3. Generate an API key if you haven't already +4. Note your organization ID from the URL or settings page + +## Validation + +You can validate your environment variables in your application: + +```typescript +const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, +}; + +if (!config.authorization || !config.organizationId) { + throw new Error('Missing required Vectorize credentials'); +} +``` + +## Next Steps + +- [Creating Connectors](../../creating-connectors/vectorize/) +- [Authentication](../../authentication/vectorize/) diff --git a/docs/environment-setup/white-label/README.md b/docs/environment-setup/white-label/README.md new file mode 100644 index 0000000..6fd8a32 --- /dev/null +++ b/docs/environment-setup/white-label/README.md @@ -0,0 +1,71 @@ +# Environment Setup - White-Label Approach + +When using your own OAuth credentials, you need to configure both Vectorize API credentials and platform-specific OAuth credentials. + +## Required Environment Variables + +Add the following environment variables to your Next.js application: + +```env +# Vectorize credentials +VECTORIZE_API_KEY=your_vectorize_api_token +VECTORIZE_ORGANIZATION_ID=your_organization_id + +# Platform-specific OAuth credentials (example for Google Drive) +GOOGLE_OAUTH_CLIENT_ID=your_google_client_id +GOOGLE_OAUTH_CLIENT_SECRET=your_google_client_secret +GOOGLE_API_KEY=your_google_api_key + +# Platform-specific OAuth credentials (example for Dropbox) +DROPBOX_APP_KEY=your_dropbox_app_key +DROPBOX_APP_SECRET=your_dropbox_app_secret +``` + +## Platform-Specific Setup + +### Google Drive +1. Go to the [Google Cloud Console](https://console.cloud.google.com/) +2. Create a new project or select an existing one +3. Enable the Google Drive API +4. Configure the OAuth consent screen +5. Create OAuth 2.0 credentials (Web application type) +6. Add your redirect URI (e.g., `https://your-app.com/api/oauth/callback`) +7. Create an API key for the Google Picker API + +### Dropbox +1. Go to the [Dropbox Developer Console](https://www.dropbox.com/developers/apps) +2. Click "Create app" +3. Choose "Scoped access" for API +4. Choose "Full Dropbox" or "App folder" access depending on your needs +5. Name your app +6. Under "OAuth 2", add your redirect URI (e.g., `https://your-app.com/api/dropbox-callback`) +7. Note your App Key and App Secret + +## Validation + +```typescript +const vectorizeConfig = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, +}; + +// Platform-specific validation +const googleConfig = { + clientId: process.env.GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET!, + apiKey: process.env.GOOGLE_API_KEY!, +}; + +if (!vectorizeConfig.authorization || !vectorizeConfig.organizationId) { + throw new Error('Missing required Vectorize credentials'); +} + +if (!googleConfig.clientId || !googleConfig.clientSecret || !googleConfig.apiKey) { + throw new Error('Missing required Google OAuth credentials'); +} +``` + +## Next Steps + +- [Creating Connectors](../../creating-connectors/white-label/) +- [Authentication](../../authentication/white-label/) diff --git a/docs/frontend-implementation/README.md b/docs/frontend-implementation/README.md new file mode 100644 index 0000000..64cf022 --- /dev/null +++ b/docs/frontend-implementation/README.md @@ -0,0 +1,69 @@ +# Frontend Implementation + +This section covers frontend implementation patterns for different connector approaches. + +## Approaches + +- **[Vectorize](./vectorize/)** - Frontend components using Vectorize's managed flow +- **[White-Label](./white-label/)** - Frontend components using custom OAuth flows + +## Overview + +Frontend implementation involves creating user interfaces that allow users to connect their cloud storage accounts and manage their file selections. + +### Vectorize Approach +- Simplified components that redirect to Vectorize's platform +- Consistent user experience across all platforms +- Minimal frontend code required + +### White-Label Approach +- Custom components with full control over user experience +- Platform-specific OAuth handling +- More complex but fully customizable + +## Common Patterns + +### Component Structure +```typescript +export default function ConnectorComponent() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + // Component logic here + + return ( +
+ {/* UI elements */} +
+ ); +} +``` + +### Error Handling +```typescript +{error && ( +
+ {error} +
+)} +``` + +### Loading States +```typescript + +``` + +## Best Practices + +1. **Error Handling**: Always provide clear error messages to users +2. **Loading States**: Show loading indicators during async operations +3. **Validation**: Validate user inputs before making API calls +4. **Accessibility**: Use proper ARIA labels and semantic HTML +5. **Responsive Design**: Ensure components work on all screen sizes diff --git a/docs/frontend-implementation/vectorize/README.md b/docs/frontend-implementation/vectorize/README.md new file mode 100644 index 0000000..018a628 --- /dev/null +++ b/docs/frontend-implementation/vectorize/README.md @@ -0,0 +1,328 @@ +# Frontend Implementation - Vectorize Approach + +Frontend components for Vectorize connectors with simplified authentication flow. + +## Basic Connector Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { PlatformOAuth } from '@vectorize-io/vectorize-connect'; + +export default function VectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleCreateConnector = async () => { + try { + const response = await fetch("/api/createConnector", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorName: "My Team Storage Connector", + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create connector'); + } + + const { connectorId } = await response.json(); + setConnectorId(connectorId); + } catch (error: any) { + setError(error.message); + } + }; + + const handleConnect = async () => { + setIsLoading(true); + setError(null); + + try { + // Get one-time token from API endpoint + const tokenResponse = await fetch( + `/api/get-one-time-connector-token?userId=user123&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize authentication flow + await PlatformOAuth.redirectToVectorizeConnect( + tokenResponse.token, + 'your-org-id' + ); + + setIsLoading(false); + } catch (err: any) { + const errorMessage = err instanceof Error ? err.message : 'Failed to connect'; + setError(errorMessage); + setIsLoading(false); + } + }; + + return ( +
+

Platform Connection

+ + {error && ( +
+

{error}

+
+ )} + +
+ + + +
+ + {connectorId && ( +
+

Connector ID:

+

+ {connectorId} +

+
+ )} +
+ ); +} +``` + +## Advanced Component with User Management + +```typescript +'use client'; + +import { useState, useEffect } from 'react'; +import { PlatformOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +interface User { + id: string; + name: string; + email: string; + fileCount: number; +} + +export default function AdvancedVectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleAddUser = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-one-time-connector-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error('Failed to generate token'); + } + return response.json(); + }); + + await PlatformOAuth.redirectToVectorizeConnect( + tokenResponse.token, + 'your-org-id' + ); + + setIsLoading(false); + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const handleEditUser = async (userId: string) => { + try { + const tokenResponse = await fetch( + `/api/get-one-time-connector-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => response.json()); + + await PlatformOAuth.redirectToVectorizeEdit( + tokenResponse.token, + 'your-org-id' + ); + } catch (error: any) { + setError(error.message); + } + }; + + const handleRemoveUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove user'); + } + + setUsers(users.filter(user => user.id !== userId)); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+
+

Connector Management

+ + {error && ( +
+ {error} +
+ )} + +
+ +
+
+ + {users.length > 0 && ( +
+

Connected Users

+
+ {users.map(user => ( +
+
+

{user.name}

+

{user.email}

+

{user.fileCount} files

+
+
+ + +
+
+ ))} +
+
+ )} +
+ ); +} +``` + +## Hooks for Reusable Logic + +```typescript +// hooks/useVectorizeConnector.ts +import { useState } from 'react'; +import { PlatformOAuth } from '@vectorize-io/vectorize-connect'; + +export function useVectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const createConnector = async (name: string) => { + try { + const response = await fetch("/api/createConnector", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ connectorName: name }), + }); + + if (!response.ok) { + throw new Error('Failed to create connector'); + } + + const { connectorId } = await response.json(); + setConnectorId(connectorId); + return connectorId; + } catch (error: any) { + setError(error.message); + throw error; + } + }; + + const connectUser = async (userId: string, organizationId: string) => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-one-time-connector-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => response.json()); + + await PlatformOAuth.redirectToVectorizeConnect( + tokenResponse.token, + organizationId + ); + + setIsLoading(false); + } catch (error: any) { + setError(error.message); + setIsLoading(false); + throw error; + } + }; + + return { + connectorId, + isLoading, + error, + createConnector, + connectUser, + setError + }; +} +``` + +## Platform-Specific Examples + +For detailed platform-specific frontend implementation examples: + +- [Google Drive Frontend Implementation](./google-drive.md) +- [Dropbox Frontend Implementation](./dropbox.md) +- [Notion Frontend Implementation](./notion.md) + +## Next Steps + +- [Testing](../../testing/vectorize/) +- [User Management](../../user-management/vectorize/) diff --git a/docs/frontend-implementation/vectorize/dropbox.md b/docs/frontend-implementation/vectorize/dropbox.md new file mode 100644 index 0000000..c38534d --- /dev/null +++ b/docs/frontend-implementation/vectorize/dropbox.md @@ -0,0 +1,325 @@ +# Dropbox Frontend Implementation - Vectorize Approach + +Frontend components for Dropbox Vectorize connectors. + +## Basic Dropbox Connector Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { DropboxOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +export default function DropboxVectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleCreateConnector = async () => { + try { + const response = await fetch("/api/createDropboxConnector", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorName: "My Dropbox Connector", + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create Dropbox connector'); + } + + const { connectorId } = await response.json(); + setConnectorId(connectorId); + } catch (error: any) { + setError(error.message); + } + }; + + const handleConnect = async () => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-dropbox-token?userId=user123&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + await DropboxOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + setIsLoading(false); + } catch (err: any) { + const errorMessage = err instanceof Error ? err.message : 'Failed to connect'; + setError(errorMessage); + setIsLoading(false); + } + }; + + return ( +
+
+
+ DB +
+

Dropbox Connection

+
+ + {error && ( +
+

{error}

+
+ )} + +
+ + + +
+ + {connectorId && ( +
+

Dropbox Connector ID:

+

+ {connectorId} +

+
+ )} +
+ ); +} +``` + +## Advanced Dropbox Component with User Management + +```typescript +'use client'; + +import { useState, useEffect } from 'react'; +import { DropboxOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +interface DropboxUser { + id: string; + name: string; + email: string; + fileCount: number; + lastSync: string; +} + +export default function AdvancedDropboxConnector() { + const [connectorId, setConnectorId] = useState(null); + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleAddUser = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-dropbox-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error('Failed to generate Dropbox token'); + } + return response.json(); + }); + + await DropboxOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + setIsLoading(false); + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const handleEditUser = async (userId: string) => { + try { + const tokenResponse = await fetch( + `/api/get-dropbox-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => response.json()); + + await DropboxOAuth.redirectToVectorizeEdit( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + } catch (error: any) { + setError(error.message); + } + }; + + const handleRemoveUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-dropbox-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Dropbox user'); + } + + setUsers(users.filter(user => user.id !== userId)); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+
+
+
+ DB +
+

Dropbox Connector Management

+
+ + {error && ( +
+ {error} +
+ )} + +
+ +
+
+ + {users.length > 0 && ( +
+

Connected Dropbox Users

+
+ {users.map(user => ( +
+
+

{user.name}

+

{user.email}

+

{user.fileCount} Dropbox files

+

Last sync: {user.lastSync}

+
+
+ + +
+
+ ))} +
+
+ )} +
+ ); +} +``` + +## Dropbox Hook for Reusable Logic + +```typescript +// hooks/useDropboxConnector.ts +import { useState } from 'react'; +import { DropboxOAuth, createVectorizeDropboxConnector } from '@vectorize-io/vectorize-connect'; + +export function useDropboxConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const createConnector = async (name: string) => { + try { + const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! + }; + + const connectorId = await createVectorizeDropboxConnector(config, name); + setConnectorId(connectorId); + return connectorId; + } catch (error: any) { + setError(error.message); + throw error; + } + }; + + const connectUser = async (userId: string, organizationId: string) => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-dropbox-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => response.json()); + + await DropboxOAuth.redirectToVectorizeConnect( + tokenResponse.token, + organizationId + ); + + setIsLoading(false); + } catch (error: any) { + setError(error.message); + setIsLoading(false); + throw error; + } + }; + + return { + connectorId, + isLoading, + error, + createConnector, + connectUser, + setError + }; +} +``` + +## Next Steps + +- [Dropbox User Management](../../user-management/vectorize/dropbox.md) +- [Dropbox Testing](../../testing/vectorize/dropbox.md) diff --git a/docs/frontend-implementation/vectorize/google-drive.md b/docs/frontend-implementation/vectorize/google-drive.md new file mode 100644 index 0000000..7058a9c --- /dev/null +++ b/docs/frontend-implementation/vectorize/google-drive.md @@ -0,0 +1,325 @@ +# Google Drive Frontend Implementation - Vectorize Approach + +Frontend components for Google Drive Vectorize connectors. + +## Basic Google Drive Connector Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { GoogleDriveOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +export default function GoogleDriveVectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleCreateConnector = async () => { + try { + const response = await fetch("/api/createGDriveConnector", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorName: "My Google Drive Connector", + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create Google Drive connector'); + } + + const { connectorId } = await response.json(); + setConnectorId(connectorId); + } catch (error: any) { + setError(error.message); + } + }; + + const handleConnect = async () => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-gdrive-token?userId=user123&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + await GoogleDriveOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + setIsLoading(false); + } catch (err: any) { + const errorMessage = err instanceof Error ? err.message : 'Failed to connect'; + setError(errorMessage); + setIsLoading(false); + } + }; + + return ( +
+
+
+ GD +
+

Google Drive Connection

+
+ + {error && ( +
+

{error}

+
+ )} + +
+ + + +
+ + {connectorId && ( +
+

Google Drive Connector ID:

+

+ {connectorId} +

+
+ )} +
+ ); +} +``` + +## Advanced Google Drive Component with User Management + +```typescript +'use client'; + +import { useState, useEffect } from 'react'; +import { GoogleDriveOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +interface GoogleDriveUser { + id: string; + name: string; + email: string; + fileCount: number; + lastSync: string; +} + +export default function AdvancedGoogleDriveConnector() { + const [connectorId, setConnectorId] = useState(null); + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleAddUser = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-gdrive-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error('Failed to generate Google Drive token'); + } + return response.json(); + }); + + await GoogleDriveOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + setIsLoading(false); + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const handleEditUser = async (userId: string) => { + try { + const tokenResponse = await fetch( + `/api/get-gdrive-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => response.json()); + + await GoogleDriveOAuth.redirectToVectorizeEdit( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + } catch (error: any) { + setError(error.message); + } + }; + + const handleRemoveUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-gdrive-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Google Drive user'); + } + + setUsers(users.filter(user => user.id !== userId)); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+
+
+
+ GD +
+

Google Drive Connector Management

+
+ + {error && ( +
+ {error} +
+ )} + +
+ +
+
+ + {users.length > 0 && ( +
+

Connected Google Drive Users

+
+ {users.map(user => ( +
+
+

{user.name}

+

{user.email}

+

{user.fileCount} Google Drive files

+

Last sync: {user.lastSync}

+
+
+ + +
+
+ ))} +
+
+ )} +
+ ); +} +``` + +## Google Drive Hook for Reusable Logic + +```typescript +// hooks/useGoogleDriveConnector.ts +import { useState } from 'react'; +import { GoogleDriveOAuth, createVectorizeGDriveConnector } from '@vectorize-io/vectorize-connect'; + +export function useGoogleDriveConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const createConnector = async (name: string) => { + try { + const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! + }; + + const connectorId = await createVectorizeGDriveConnector(config, name); + setConnectorId(connectorId); + return connectorId; + } catch (error: any) { + setError(error.message); + throw error; + } + }; + + const connectUser = async (userId: string, organizationId: string) => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-gdrive-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => response.json()); + + await GoogleDriveOAuth.redirectToVectorizeConnect( + tokenResponse.token, + organizationId + ); + + setIsLoading(false); + } catch (error: any) { + setError(error.message); + setIsLoading(false); + throw error; + } + }; + + return { + connectorId, + isLoading, + error, + createConnector, + connectUser, + setError + }; +} +``` + +## Next Steps + +- [Google Drive User Management](../../user-management/vectorize/google-drive.md) +- [Google Drive Testing](../../testing/vectorize/google-drive.md) diff --git a/docs/frontend-implementation/vectorize/notion.md b/docs/frontend-implementation/vectorize/notion.md new file mode 100644 index 0000000..e0714df --- /dev/null +++ b/docs/frontend-implementation/vectorize/notion.md @@ -0,0 +1,327 @@ +# Notion Frontend Implementation - Vectorize Approach + +Frontend components for Notion Vectorize connectors. + +## Basic Notion Connector Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { NotionOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +export default function NotionVectorizeConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleCreateConnector = async () => { + try { + const response = await fetch("/api/createNotionConnector", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorName: "My Notion Connector", + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create Notion connector'); + } + + const { connectorId } = await response.json(); + setConnectorId(connectorId); + } catch (error: any) { + setError(error.message); + } + }; + + const handleConnect = async () => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-notion-token?userId=user123&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + await NotionOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + setIsLoading(false); + } catch (err: any) { + const errorMessage = err instanceof Error ? err.message : 'Failed to connect'; + setError(errorMessage); + setIsLoading(false); + } + }; + + return ( +
+
+
+ N +
+

Notion Connection

+
+ + {error && ( +
+

{error}

+
+ )} + +
+ + + +
+ + {connectorId && ( +
+

Notion Connector ID:

+

+ {connectorId} +

+
+ )} +
+ ); +} +``` + +## Advanced Notion Component with User Management + +```typescript +'use client'; + +import { useState, useEffect } from 'react'; +import { NotionOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +interface NotionUser { + id: string; + workspaceName: string; + pageCount: number; + lastSync: string; + selectedPages: Record; +} + +export default function AdvancedNotionConnector() { + const [connectorId, setConnectorId] = useState(null); + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleAddUser = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-notion-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error('Failed to generate Notion token'); + } + return response.json(); + }); + + await NotionOAuth.redirectToVectorizeConnect( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + setIsLoading(false); + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const handleEditUser = async (userId: string) => { + try { + const tokenResponse = await fetch( + `/api/get-notion-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => response.json()); + + await NotionOAuth.redirectToVectorizeEdit( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + } catch (error: any) { + setError(error.message); + } + }; + + const handleRemoveUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-notion-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Notion user'); + } + + setUsers(users.filter(user => user.id !== userId)); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+
+
+
+ N +
+

Notion Connector Management

+
+ + {error && ( +
+ {error} +
+ )} + +
+ +
+
+ + {users.length > 0 && ( +
+

Connected Notion Users

+
+ {users.map(user => ( +
+
+

{user.workspaceName}

+

{user.pageCount} Notion pages

+

Last sync: {user.lastSync}

+
+ Pages: {Object.values(user.selectedPages).map(page => page.title).join(', ')} +
+
+
+ + +
+
+ ))} +
+
+ )} +
+ ); +} +``` + +## Notion Hook for Reusable Logic + +```typescript +// hooks/useNotionConnector.ts +import { useState } from 'react'; +import { NotionOAuth, createVectorizeNotionConnector } from '@vectorize-io/vectorize-connect'; + +export function useNotionConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const createConnector = async (name: string) => { + try { + const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! + }; + + const connectorId = await createVectorizeNotionConnector(config, name); + setConnectorId(connectorId); + return connectorId; + } catch (error: any) { + setError(error.message); + throw error; + } + }; + + const connectUser = async (userId: string, organizationId: string) => { + setIsLoading(true); + setError(null); + + try { + const tokenResponse = await fetch( + `/api/get-notion-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => response.json()); + + await NotionOAuth.redirectToVectorizeConnect( + tokenResponse.token, + organizationId + ); + + setIsLoading(false); + } catch (error: any) { + setError(error.message); + setIsLoading(false); + throw error; + } + }; + + return { + connectorId, + isLoading, + error, + createConnector, + connectUser, + setError + }; +} +``` + +## Next Steps + +- [Notion User Management](../../user-management/vectorize/notion.md) +- [Notion Testing](../../testing/vectorize/notion.md) diff --git a/docs/frontend-implementation/white-label/README.md b/docs/frontend-implementation/white-label/README.md new file mode 100644 index 0000000..6bb20e5 --- /dev/null +++ b/docs/frontend-implementation/white-label/README.md @@ -0,0 +1,402 @@ +# Frontend Implementation - White-Label Approach + +Frontend components for white-label connectors with custom OAuth flows. + +## Google Drive Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { GoogleDriveOAuth, GoogleDriveSelection } from '@vectorize-io/vectorize-connect'; + +export default function GoogleDriveConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [selectedFiles, setSelectedFiles] = useState(null); + const [refreshToken, setRefreshToken] = useState(null); + + const handleCreateConnector = async () => { + try { + const response = await fetch("/api/createGDriveConnector", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorName: "My Custom Google Drive Connector", + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create connector'); + } + + const { connectorId } = await response.json(); + setConnectorId(connectorId); + } catch (error: any) { + setError(error.message); + } + }; + + const handleConnectGoogleDrive = async () => { + setIsLoading(true); + setError(null); + + try { + const config = { + clientId: process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: '', + apiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY!, + redirectUri: `${window.location.origin}/api/oauth/callback`, + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: (response) => { + setSelectedFiles(response.fileIds); + setRefreshToken(response.refreshToken); + setIsLoading(false); + + if (connectorId) { + addUserToConnector(connectorId, response.fileIds, response.refreshToken); + } + }, + onError: (error) => { + setError(error.message); + setIsLoading(false); + } + }; + + GoogleDriveOAuth.startOAuth(config); + + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const handleSelectMoreFiles = async () => { + if (!refreshToken) { + setError('No refresh token available. Please connect to Google Drive first.'); + return; + } + + try { + const config = { + clientId: process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CLIENT_ID!, + clientSecret: '', + apiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY!, + onSuccess: (response) => { + setSelectedFiles(response.fileIds); + if (connectorId) { + addUserToConnector(connectorId, response.fileIds, refreshToken); + } + }, + onError: (error) => { + setError(error.message); + } + }; + + await GoogleDriveSelection.startFileSelection(config, refreshToken); + + } catch (error: any) { + setError(error.message); + } + }; + + const addUserToConnector = async (connectorId: string, fileIds: string[], refreshToken: string) => { + try { + const response = await fetch("/api/manage-gdrive-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + fileIds, + refreshToken, + userId: "user123", + action: "add" + }), + }); + + if (!response.ok) { + throw new Error('Failed to add user to connector'); + } + + console.log('User added to connector successfully'); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+

Google Drive Connection

+ + {error && ( +
+

{error}

+
+ )} + +
+ + + + + {refreshToken && ( + + )} +
+ + {selectedFiles && ( +
+

Selected Files:

+
+            {JSON.stringify(selectedFiles, null, 2)}
+          
+
+ )} + + {connectorId && ( +
+

Connector ID:

+

+ {connectorId} +

+
+ )} +
+ ); +} +``` + +## Dropbox Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { DropboxOAuth } from '@vectorize-io/vectorize-connect'; + +export default function DropboxConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [selectedFiles, setSelectedFiles] = useState | null>(null); + const [refreshToken, setRefreshToken] = useState(null); + + const handleCreateConnector = async () => { + try { + const response = await fetch("/api/createDropboxConnector", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorName: "My Custom Dropbox Connector", + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create connector'); + } + + const { connectorId } = await response.json(); + setConnectorId(connectorId); + } catch (error: any) { + setError(error.message); + } + }; + + const handleConnectDropbox = async () => { + setIsLoading(true); + setError(null); + + try { + const config = { + appKey: process.env.NEXT_PUBLIC_DROPBOX_APP_KEY!, + appSecret: '', + redirectUri: `${window.location.origin}/api/dropbox-callback`, + scopes: ['files.metadata.read', 'files.content.read'], + onSuccess: (selection) => { + setSelectedFiles(selection.selectedFiles); + setRefreshToken(selection.refreshToken); + setIsLoading(false); + + if (connectorId) { + addUserToConnector(connectorId, selection.selectedFiles, selection.refreshToken); + } + }, + onError: (error) => { + setError(error.message); + setIsLoading(false); + } + }; + + DropboxOAuth.startOAuth(config); + + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const addUserToConnector = async (connectorId: string, selectedFiles: Record, refreshToken: string) => { + try { + const response = await fetch("/api/manage-dropbox-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + selectedFiles, + refreshToken, + userId: "user123", + action: "add" + }), + }); + + if (!response.ok) { + throw new Error('Failed to add user to connector'); + } + + console.log('User added to connector successfully'); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+

Dropbox Connection

+ + {error && ( +
+

{error}

+
+ )} + +
+ + + +
+ + {selectedFiles && ( +
+

Selected Files:

+
+            {JSON.stringify(selectedFiles, null, 2)}
+          
+
+ )} + + {connectorId && ( +
+

Connector ID:

+

+ {connectorId} +

+
+ )} +
+ ); +} +``` + +## Multi-Platform Component + +```typescript +'use client'; + +import { useState } from 'react'; +import GoogleDriveConnector from './GoogleDriveConnector'; +import DropboxConnector from './DropboxConnector'; + +type Platform = 'google-drive' | 'dropbox'; + +export default function MultiPlatformConnector() { + const [selectedPlatform, setSelectedPlatform] = useState(null); + + const platforms = [ + { id: 'google-drive' as Platform, name: 'Google Drive', icon: '📁' }, + { id: 'dropbox' as Platform, name: 'Dropbox', icon: '📦' }, + ]; + + if (selectedPlatform) { + return ( +
+ + + {selectedPlatform === 'google-drive' && } + {selectedPlatform === 'dropbox' && } +
+ ); + } + + return ( +
+

+ Choose Your Platform +

+ +
+ {platforms.map(platform => ( + + ))} +
+
+ ); +} +``` + +## Platform-Specific Examples + +For detailed platform-specific frontend implementation examples: + +- [Google Drive Frontend Implementation](./google-drive.md) +- [Dropbox Frontend Implementation](./dropbox.md) +- [Notion Frontend Implementation](./notion.md) + +## Next Steps + +- [Testing](../../testing/white-label/) +- [User Management](../../user-management/white-label/) diff --git a/docs/frontend-implementation/white-label/dropbox.md b/docs/frontend-implementation/white-label/dropbox.md new file mode 100644 index 0000000..b83d272 --- /dev/null +++ b/docs/frontend-implementation/white-label/dropbox.md @@ -0,0 +1,439 @@ +# Dropbox Frontend Implementation - White-Label Approach + +Frontend components for Dropbox White-Label connectors. + +## Basic Dropbox White-Label Component + +```typescript +import React, { useState } from 'react'; +import { createWhiteLabelDropboxConnector, DropboxOAuth, manageUser } from '@vectorize-io/vectorize-connect'; + +interface DropboxWhiteLabelProps { + onConnectorCreated?: (connectorId: string) => void; + onUserAdded?: (userId: string) => void; +} + +const DropboxWhiteLabel: React.FC = ({ + onConnectorCreated, + onUserAdded +}) => { + const [connectorId, setConnectorId] = useState(''); + const [isCreating, setIsCreating] = useState(false); + const [isConnecting, setIsConnecting] = useState(false); + const [error, setError] = useState(''); + + const createConnector = async () => { + setIsCreating(true); + setError(''); + + try { + const config = { + authorization: process.env.NEXT_PUBLIC_VECTORIZE_API_KEY!, + organizationId: process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + }; + + const newConnectorId = await createWhiteLabelDropboxConnector( + config, + 'Dropbox White-Label Connector', + process.env.NEXT_PUBLIC_DROPBOX_APP_KEY!, + process.env.NEXT_PUBLIC_DROPBOX_APP_SECRET! + ); + + setConnectorId(newConnectorId); + onConnectorCreated?.(newConnectorId); + } catch (err: any) { + setError(err.message || 'Failed to create Dropbox connector'); + } finally { + setIsCreating(false); + } + }; + + const connectUser = async (userId: string) => { + if (!connectorId) return; + + setIsConnecting(true); + setError(''); + + try { + const oauthConfig = { + appKey: process.env.NEXT_PUBLIC_DROPBOX_APP_KEY!, + appSecret: process.env.NEXT_PUBLIC_DROPBOX_APP_SECRET!, + redirectUri: `${window.location.origin}/api/dropbox-callback`, + scopes: ['files.metadata.read', 'files.content.read'], + onSuccess: async (response: any) => { + const vectorizeConfig = { + authorization: process.env.NEXT_PUBLIC_VECTORIZE_API_KEY!, + organizationId: process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + }; + + await manageUser( + vectorizeConfig, + connectorId, + userId, + 'add', + { + selectedFiles: response.selectedFiles, + accessToken: response.accessToken + } + ); + + onUserAdded?.(userId); + setIsConnecting(false); + }, + onError: (error: any) => { + setError(error.message || 'OAuth failed'); + setIsConnecting(false); + } + }; + + DropboxOAuth.startOAuth(oauthConfig); + } catch (err: any) { + setError(err.message || 'Failed to start OAuth flow'); + setIsConnecting(false); + } + }; + + return ( +
+

Dropbox White-Label Connector

+ + {!connectorId ? ( + + ) : ( +
+

Connector Created: {connectorId}

+ +
+ )} + + {error && ( +
+ Error: {error} +
+ )} +
+ ); +}; + +export default DropboxWhiteLabel; +``` + +## Advanced Dropbox White-Label Component + +```typescript +import React, { useState } from 'react'; +import { + createWhiteLabelDropboxConnector, + DropboxOAuth, + manageUser +} from '@vectorize-io/vectorize-connect'; + +interface User { + id: string; + name: string; + email: string; + status: 'connected' | 'pending' | 'error'; + selectedFiles?: any[]; +} + +const AdvancedDropboxWhiteLabel: React.FC = () => { + const [connectorId, setConnectorId] = useState(''); + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + + const vectorizeConfig = { + authorization: process.env.NEXT_PUBLIC_VECTORIZE_API_KEY!, + organizationId: process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + }; + + const createConnector = async () => { + setIsLoading(true); + try { + const newConnectorId = await createWhiteLabelDropboxConnector( + vectorizeConfig, + 'Advanced Dropbox White-Label', + process.env.NEXT_PUBLIC_DROPBOX_APP_KEY!, + process.env.NEXT_PUBLIC_DROPBOX_APP_SECRET! + ); + setConnectorId(newConnectorId); + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + const addUser = async (userId: string, userName: string, userEmail: string) => { + if (!connectorId) return; + + const newUser: User = { + id: userId, + name: userName, + email: userEmail, + status: 'pending' + }; + + setUsers(prev => [...prev, newUser]); + + const oauthConfig = { + appKey: process.env.NEXT_PUBLIC_DROPBOX_APP_KEY!, + appSecret: process.env.NEXT_PUBLIC_DROPBOX_APP_SECRET!, + redirectUri: `${window.location.origin}/api/dropbox-callback`, + scopes: ['files.metadata.read', 'files.content.read'], + onSuccess: async (response: any) => { + try { + await manageUser( + vectorizeConfig, + connectorId, + userId, + 'add', + { + selectedFiles: response.selectedFiles, + accessToken: response.accessToken + } + ); + + setUsers(prev => + prev.map(user => + user.id === userId + ? { ...user, status: 'connected', selectedFiles: response.selectedFiles } + : user + ) + ); + } catch (err: any) { + setUsers(prev => + prev.map(user => + user.id === userId + ? { ...user, status: 'error' } + : user + ) + ); + setError(err.message); + } + }, + onError: (error: any) => { + setUsers(prev => + prev.map(user => + user.id === userId + ? { ...user, status: 'error' } + : user + ) + ); + setError(error.message); + } + }; + + DropboxOAuth.startOAuth(oauthConfig); + }; + + const removeUser = async (userId: string) => { + if (!connectorId) return; + + try { + await manageUser(vectorizeConfig, connectorId, userId, 'remove'); + setUsers(prev => prev.filter(user => user.id !== userId)); + } catch (err: any) { + setError(err.message); + } + }; + + return ( +
+

Advanced Dropbox White-Label Connector

+ + {!connectorId ? ( + + ) : ( +
+

Connector ID: {connectorId}

+ +
+

Users

+ + +
+ {users.map(user => ( +
+
+ {user.name} ({user.email}) + + {user.status} + + {user.selectedFiles && ( + + {user.selectedFiles.length} files selected + + )} +
+
+ +
+
+ ))} +
+
+
+ )} + + {error && ( +
+ Error: {error} +
+ )} +
+ ); +}; + +export default AdvancedDropboxWhiteLabel; +``` + +## Reusable Dropbox White-Label Hook + +```typescript +import { useState, useCallback } from 'react'; +import { + createWhiteLabelDropboxConnector, + DropboxOAuth, + manageUser +} from '@vectorize-io/vectorize-connect'; + +interface UseDropboxWhiteLabelOptions { + appKey: string; + appSecret: string; + onConnectorCreated?: (connectorId: string) => void; + onUserAdded?: (userId: string) => void; + onError?: (error: string) => void; +} + +export const useDropboxWhiteLabel = (options: UseDropboxWhiteLabelOptions) => { + const [connectorId, setConnectorId] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + + const vectorizeConfig = { + authorization: process.env.NEXT_PUBLIC_VECTORIZE_API_KEY!, + organizationId: process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + }; + + const createConnector = useCallback(async (name: string = 'Dropbox White-Label Connector') => { + setIsLoading(true); + setError(''); + + try { + const newConnectorId = await createWhiteLabelDropboxConnector( + vectorizeConfig, + name, + options.appKey, + options.appSecret + ); + setConnectorId(newConnectorId); + options.onConnectorCreated?.(newConnectorId); + return newConnectorId; + } catch (err: any) { + const errorMessage = err.message || 'Failed to create Dropbox connector'; + setError(errorMessage); + options.onError?.(errorMessage); + throw err; + } finally { + setIsLoading(false); + } + }, [options, vectorizeConfig]); + + const addUser = useCallback(async (userId: string) => { + if (!connectorId) throw new Error('No connector created'); + + return new Promise((resolve, reject) => { + const oauthConfig = { + appKey: options.appKey, + appSecret: options.appSecret, + redirectUri: `${window.location.origin}/api/dropbox-callback`, + scopes: ['files.metadata.read', 'files.content.read'], + onSuccess: async (response: any) => { + try { + await manageUser( + vectorizeConfig, + connectorId, + userId, + 'add', + { + selectedFiles: response.selectedFiles, + accessToken: response.accessToken + } + ); + options.onUserAdded?.(userId); + resolve(); + } catch (err: any) { + const errorMessage = err.message || 'Failed to add user'; + setError(errorMessage); + options.onError?.(errorMessage); + reject(err); + } + }, + onError: (error: any) => { + const errorMessage = error.message || 'OAuth failed'; + setError(errorMessage); + options.onError?.(errorMessage); + reject(error); + } + }; + + DropboxOAuth.startOAuth(oauthConfig); + }); + }, [connectorId, options, vectorizeConfig]); + + const removeUser = useCallback(async (userId: string) => { + if (!connectorId) throw new Error('No connector created'); + + try { + await manageUser(vectorizeConfig, connectorId, userId, 'remove'); + } catch (err: any) { + const errorMessage = err.message || 'Failed to remove user'; + setError(errorMessage); + options.onError?.(errorMessage); + throw err; + } + }, [connectorId, options, vectorizeConfig]); + + return { + connectorId, + isLoading, + error, + createConnector, + addUser, + removeUser + }; +}; +``` + +## Next Steps + +- [Dropbox User Management](../../user-management/white-label/dropbox.md) +- [Dropbox Testing](../../testing/white-label/dropbox.md) diff --git a/docs/frontend-implementation/white-label/google-drive.md b/docs/frontend-implementation/white-label/google-drive.md new file mode 100644 index 0000000..82fb25f --- /dev/null +++ b/docs/frontend-implementation/white-label/google-drive.md @@ -0,0 +1,439 @@ +# Google Drive Frontend Implementation - White-Label Approach + +Frontend components for Google Drive White-Label connectors. + +## Basic Google Drive White-Label Component + +```typescript +import React, { useState } from 'react'; +import { createWhiteLabelGDriveConnector, GoogleDriveOAuth, manageUser } from '@vectorize-io/vectorize-connect'; + +interface GoogleDriveWhiteLabelProps { + onConnectorCreated?: (connectorId: string) => void; + onUserAdded?: (userId: string) => void; +} + +const GoogleDriveWhiteLabel: React.FC = ({ + onConnectorCreated, + onUserAdded +}) => { + const [connectorId, setConnectorId] = useState(''); + const [isCreating, setIsCreating] = useState(false); + const [isConnecting, setIsConnecting] = useState(false); + const [error, setError] = useState(''); + + const createConnector = async () => { + setIsCreating(true); + setError(''); + + try { + const config = { + authorization: process.env.NEXT_PUBLIC_VECTORIZE_API_KEY!, + organizationId: process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + }; + + const newConnectorId = await createWhiteLabelGDriveConnector( + config, + 'Google Drive White-Label Connector', + process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, + process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET! + ); + + setConnectorId(newConnectorId); + onConnectorCreated?.(newConnectorId); + } catch (err: any) { + setError(err.message || 'Failed to create Google Drive connector'); + } finally { + setIsCreating(false); + } + }; + + const connectUser = async (userId: string) => { + if (!connectorId) return; + + setIsConnecting(true); + setError(''); + + try { + const oauthConfig = { + clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, + clientSecret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET!, + redirectUri: `${window.location.origin}/api/gdrive-callback`, + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: async (response: any) => { + const vectorizeConfig = { + authorization: process.env.NEXT_PUBLIC_VECTORIZE_API_KEY!, + organizationId: process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + }; + + await manageUser( + vectorizeConfig, + connectorId, + userId, + 'add', + { + selectedFiles: response.selectedFiles, + accessToken: response.accessToken + } + ); + + onUserAdded?.(userId); + setIsConnecting(false); + }, + onError: (error: any) => { + setError(error.message || 'OAuth failed'); + setIsConnecting(false); + } + }; + + GoogleDriveOAuth.startOAuth(oauthConfig); + } catch (err: any) { + setError(err.message || 'Failed to start OAuth flow'); + setIsConnecting(false); + } + }; + + return ( +
+

Google Drive White-Label Connector

+ + {!connectorId ? ( + + ) : ( +
+

Connector Created: {connectorId}

+ +
+ )} + + {error && ( +
+ Error: {error} +
+ )} +
+ ); +}; + +export default GoogleDriveWhiteLabel; +``` + +## Advanced Google Drive White-Label Component + +```typescript +import React, { useState } from 'react'; +import { + createWhiteLabelGDriveConnector, + GoogleDriveOAuth, + manageUser +} from '@vectorize-io/vectorize-connect'; + +interface User { + id: string; + name: string; + email: string; + status: 'connected' | 'pending' | 'error'; + selectedFiles?: any[]; +} + +const AdvancedGoogleDriveWhiteLabel: React.FC = () => { + const [connectorId, setConnectorId] = useState(''); + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + + const vectorizeConfig = { + authorization: process.env.NEXT_PUBLIC_VECTORIZE_API_KEY!, + organizationId: process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + }; + + const createConnector = async () => { + setIsLoading(true); + try { + const newConnectorId = await createWhiteLabelGDriveConnector( + vectorizeConfig, + 'Advanced Google Drive White-Label', + process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, + process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET! + ); + setConnectorId(newConnectorId); + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + const addUser = async (userId: string, userName: string, userEmail: string) => { + if (!connectorId) return; + + const newUser: User = { + id: userId, + name: userName, + email: userEmail, + status: 'pending' + }; + + setUsers(prev => [...prev, newUser]); + + const oauthConfig = { + clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, + clientSecret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET!, + redirectUri: `${window.location.origin}/api/gdrive-callback`, + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: async (response: any) => { + try { + await manageUser( + vectorizeConfig, + connectorId, + userId, + 'add', + { + selectedFiles: response.selectedFiles, + accessToken: response.accessToken + } + ); + + setUsers(prev => + prev.map(user => + user.id === userId + ? { ...user, status: 'connected', selectedFiles: response.selectedFiles } + : user + ) + ); + } catch (err: any) { + setUsers(prev => + prev.map(user => + user.id === userId + ? { ...user, status: 'error' } + : user + ) + ); + setError(err.message); + } + }, + onError: (error: any) => { + setUsers(prev => + prev.map(user => + user.id === userId + ? { ...user, status: 'error' } + : user + ) + ); + setError(error.message); + } + }; + + GoogleDriveOAuth.startOAuth(oauthConfig); + }; + + const removeUser = async (userId: string) => { + if (!connectorId) return; + + try { + await manageUser(vectorizeConfig, connectorId, userId, 'remove'); + setUsers(prev => prev.filter(user => user.id !== userId)); + } catch (err: any) { + setError(err.message); + } + }; + + return ( +
+

Advanced Google Drive White-Label Connector

+ + {!connectorId ? ( + + ) : ( +
+

Connector ID: {connectorId}

+ +
+

Users

+ + +
+ {users.map(user => ( +
+
+ {user.name} ({user.email}) + + {user.status} + + {user.selectedFiles && ( + + {user.selectedFiles.length} files selected + + )} +
+
+ +
+
+ ))} +
+
+
+ )} + + {error && ( +
+ Error: {error} +
+ )} +
+ ); +}; + +export default AdvancedGoogleDriveWhiteLabel; +``` + +## Reusable Google Drive White-Label Hook + +```typescript +import { useState, useCallback } from 'react'; +import { + createWhiteLabelGDriveConnector, + GoogleDriveOAuth, + manageUser +} from '@vectorize-io/vectorize-connect'; + +interface UseGoogleDriveWhiteLabelOptions { + clientId: string; + clientSecret: string; + onConnectorCreated?: (connectorId: string) => void; + onUserAdded?: (userId: string) => void; + onError?: (error: string) => void; +} + +export const useGoogleDriveWhiteLabel = (options: UseGoogleDriveWhiteLabelOptions) => { + const [connectorId, setConnectorId] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + + const vectorizeConfig = { + authorization: process.env.NEXT_PUBLIC_VECTORIZE_API_KEY!, + organizationId: process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + }; + + const createConnector = useCallback(async (name: string = 'Google Drive White-Label Connector') => { + setIsLoading(true); + setError(''); + + try { + const newConnectorId = await createWhiteLabelGDriveConnector( + vectorizeConfig, + name, + options.clientId, + options.clientSecret + ); + setConnectorId(newConnectorId); + options.onConnectorCreated?.(newConnectorId); + return newConnectorId; + } catch (err: any) { + const errorMessage = err.message || 'Failed to create Google Drive connector'; + setError(errorMessage); + options.onError?.(errorMessage); + throw err; + } finally { + setIsLoading(false); + } + }, [options, vectorizeConfig]); + + const addUser = useCallback(async (userId: string) => { + if (!connectorId) throw new Error('No connector created'); + + return new Promise((resolve, reject) => { + const oauthConfig = { + clientId: options.clientId, + clientSecret: options.clientSecret, + redirectUri: `${window.location.origin}/api/gdrive-callback`, + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: async (response: any) => { + try { + await manageUser( + vectorizeConfig, + connectorId, + userId, + 'add', + { + selectedFiles: response.selectedFiles, + accessToken: response.accessToken + } + ); + options.onUserAdded?.(userId); + resolve(); + } catch (err: any) { + const errorMessage = err.message || 'Failed to add user'; + setError(errorMessage); + options.onError?.(errorMessage); + reject(err); + } + }, + onError: (error: any) => { + const errorMessage = error.message || 'OAuth failed'; + setError(errorMessage); + options.onError?.(errorMessage); + reject(error); + } + }; + + GoogleDriveOAuth.startOAuth(oauthConfig); + }); + }, [connectorId, options, vectorizeConfig]); + + const removeUser = useCallback(async (userId: string) => { + if (!connectorId) throw new Error('No connector created'); + + try { + await manageUser(vectorizeConfig, connectorId, userId, 'remove'); + } catch (err: any) { + const errorMessage = err.message || 'Failed to remove user'; + setError(errorMessage); + options.onError?.(errorMessage); + throw err; + } + }, [connectorId, options, vectorizeConfig]); + + return { + connectorId, + isLoading, + error, + createConnector, + addUser, + removeUser + }; +}; +``` + +## Next Steps + +- [Google Drive User Management](../../user-management/white-label/google-drive.md) +- [Google Drive Testing](../../testing/white-label/google-drive.md) diff --git a/docs/frontend-implementation/white-label/notion.md b/docs/frontend-implementation/white-label/notion.md new file mode 100644 index 0000000..0150703 --- /dev/null +++ b/docs/frontend-implementation/white-label/notion.md @@ -0,0 +1,160 @@ +# Notion Frontend Implementation - White-Label Approach + +Frontend components for Notion White-Label connectors. + +## Basic Notion White-Label Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { NotionOAuth, NotionSelection } from '@vectorize-io/vectorize-connect'; + +export default function NotionWhiteLabelConnector() { + const [connectorId, setConnectorId] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [selectedPages, setSelectedPages] = useState | null>(null); + const [accessToken, setAccessToken] = useState(null); + + const handleCreateConnector = async () => { + try { + const response = await fetch("/api/createNotionConnector", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorName: "My Custom Notion Connector", + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to create Notion connector'); + } + + const { connectorId } = await response.json(); + setConnectorId(connectorId); + } catch (error: any) { + setError(error.message); + } + }; + + const handleConnectNotion = async () => { + setIsLoading(true); + setError(null); + + try { + const config = { + clientId: process.env.NEXT_PUBLIC_NOTION_CLIENT_ID!, + clientSecret: '', + redirectUri: `${window.location.origin}/api/notion-callback`, + onSuccess: (response) => { + setSelectedPages(response.selectedPages); + setAccessToken(response.accessToken); + setIsLoading(false); + + if (connectorId) { + addUserToConnector(connectorId, response.selectedPages, response.accessToken); + } + }, + onError: (error) => { + setError(error.message); + setIsLoading(false); + } + }; + + NotionOAuth.startOAuth(config); + + } catch (error: any) { + setError(error.message); + setIsLoading(false); + } + }; + + const addUserToConnector = async (connectorId: string, selectedPages: Record, accessToken: string) => { + try { + const response = await fetch("/api/manage-notion-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + selectedPages, + accessToken, + userId: "user123", + action: "add" + }), + }); + + if (!response.ok) { + throw new Error('Failed to add user to Notion connector'); + } + + console.log('User added to Notion connector successfully'); + } catch (error: any) { + setError(error.message); + } + }; + + return ( +
+
+
+ N +
+

Notion Connection

+
+ + {error && ( +
+

{error}

+
+ )} + +
+ + + +
+ + {selectedPages && ( +
+

Selected Pages:

+
+ {Object.values(selectedPages).map((page: any) => ( +
+ {page.title} +
+ ))} +
+
+ )} + + {connectorId && ( +
+

Connector ID:

+

+ {connectorId} +

+
+ )} +
+ ); +} +``` + +## Next Steps + +- [Notion User Management](../../user-management/white-label/notion.md) +- [Notion Testing](../../testing/white-label/notion.md) diff --git a/docs/general-guide.md b/docs/general-guide.md index f676bd0..cff4870 100644 --- a/docs/general-guide.md +++ b/docs/general-guide.md @@ -2,7 +2,7 @@ ## Overview -The Vectorize Connect SDK provides a set of tools to integrate with various data sources like Google Drive and Dropbox, enabling seamless connection between your application and Vectorize's platform. This guide covers the general concepts and usage patterns for the SDK. +The Vectorize Connect SDK provides a set of tools to integrate with various cloud storage platforms, enabling seamless connection between your application and Vectorize's platform. This guide covers the general concepts and usage patterns for the SDK. ## Installation @@ -16,7 +16,7 @@ All interactions with the Vectorize API require authentication using a Vectorize ```typescript const config = { - authorization: 'Bearer your-token', // Use VECTORIZE_TOKEN environment variable + authorization: 'Bearer your-token', // Use VECTORIZE_API_KEY environment variable organizationId: 'your-org-id' }; ``` @@ -37,8 +37,8 @@ Most functions in the SDK require a `VectorizeAPIConfig` object: ```typescript interface VectorizeAPIConfig { - authorization: string; // Bearer token for authentication - use VECTORIZE_TOKEN env var - organizationId: string; // Your Vectorize organization ID - use VECTORIZE_ORG env var + authorization: string; // Bearer token for authentication - use VECTORIZE_API_KEY env var + organizationId: string; // Your Vectorize organization ID - use VECTORIZE_ORGANIZATION_ID env var } ``` @@ -69,12 +69,26 @@ await manageUser(config, connectorId, userId); ## Next Steps -For specific data source integrations, refer to the dedicated guides: - -- [Google Drive Integration](./google-drive/index.md) - - [Vectorize Google Drive Guide](./google-drive/vectorize-guide.md) - - [White-Label Google Drive Guide](./google-drive/white-label-guide.md) -- [Dropbox Integration](./dropbox/index.md) - - [Vectorize Dropbox Guide](./dropbox/vectorize-guide.md) - - [White-Label Dropbox Guide](./dropbox/white-label-guide.md) -- [Setup Guide](./setup.md) +For specific platform integrations, refer to the step-based documentation structure: + +### Step-by-Step Implementation Guides + +#### Vectorize Approach (Managed OAuth) +- [Environment Setup](./environment-setup/vectorize/README.md) +- [Creating Connectors](./creating-connectors/vectorize/README.md) +- [Authentication](./authentication/vectorize/README.md) +- [User Management](./user-management/vectorize/README.md) +- [Frontend Implementation](./frontend-implementation/vectorize/README.md) +- [Testing](./testing/vectorize/README.md) + +#### White-Label Approach (Custom OAuth) +- [Environment Setup](./environment-setup/white-label/README.md) +- [Creating Connectors](./creating-connectors/white-label/README.md) +- [Authentication](./authentication/white-label/README.md) +- [User Management](./user-management/white-label/README.md) +- [Frontend Implementation](./frontend-implementation/white-label/README.md) +- [Testing](./testing/white-label/README.md) + +### Legacy Platform-Specific Guides (Deprecated) +- [Google Drive Integration](./legacy-docs/google-drive/) - Use step-based guides instead +- [Dropbox Integration](./legacy-docs/dropbox/) - Use step-based guides instead diff --git a/docs/dropbox/vectorize-guide.md b/docs/legacy-docs/dropbox/vectorize-guide.md similarity index 96% rename from docs/dropbox/vectorize-guide.md rename to docs/legacy-docs/dropbox/vectorize-guide.md index 6162955..ee50a92 100644 --- a/docs/dropbox/vectorize-guide.md +++ b/docs/legacy-docs/dropbox/vectorize-guide.md @@ -25,8 +25,8 @@ Add the following environment variables to your Next.js application: ```env # Vectorize credentials -VECTORIZE_ORG=your-organization-id -VECTORIZE_TOKEN=your-api-key +VECTORIZE_ORGANIZATION_ID=your-organization-id +VECTORIZE_API_KEY=your-api-key ``` ## Step 2 (Optional): Create Connector API Route @@ -53,8 +53,8 @@ export async function POST(request: Request) { // Gather environment variables for your Vectorize config const config: VectorizeAPIConfig = { - organizationId: process.env.VECTORIZE_ORG ?? "", - authorization: process.env.VECTORIZE_TOKEN ?? "", + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", }; // Validate environment variables @@ -92,8 +92,8 @@ import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { try { // Get authentication details from environment variables - const apiKey = process.env.VECTORIZE_TOKEN; - const organizationId = process.env.VECTORIZE_ORG; + const apiKey = process.env.VECTORIZE_API_KEY; + const organizationId = process.env.VECTORIZE_ORGANIZATION_ID; if (!apiKey || !organizationId) { return NextResponse.json({ diff --git a/docs/dropbox/white-label-guide.md b/docs/legacy-docs/dropbox/white-label-guide.md similarity index 97% rename from docs/dropbox/white-label-guide.md rename to docs/legacy-docs/dropbox/white-label-guide.md index f3e0b2f..d0f5c05 100644 --- a/docs/dropbox/white-label-guide.md +++ b/docs/legacy-docs/dropbox/white-label-guide.md @@ -38,8 +38,8 @@ Add the following environment variables to your Next.js application: ```env # Vectorize credentials -VECTORIZE_ORG=your-organization-id -VECTORIZE_TOKEN=your-api-key +VECTORIZE_ORGANIZATION_ID=your-organization-id +VECTORIZE_API_KEY=your-api-key # Dropbox credentials DROPBOX_APP_KEY=your-dropbox-app-key @@ -68,8 +68,8 @@ export async function POST(request: Request) { // Gather environment variables for your Vectorize config const config: VectorizeAPIConfig = { - organizationId: process.env.VECTORIZE_ORG ?? "", - authorization: process.env.VECTORIZE_TOKEN ?? "", + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", }; // Validate environment variables @@ -172,8 +172,8 @@ export async function POST(request: NextRequest) { // Configure Vectorize API const config: VectorizeAPIConfig = { - organizationId: process.env.VECTORIZE_ORG ?? "", - authorization: process.env.VECTORIZE_TOKEN ?? "", + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", }; // Manage the user diff --git a/docs/google-drive/vectorize-guide.md b/docs/legacy-docs/google-drive/vectorize-guide.md similarity index 96% rename from docs/google-drive/vectorize-guide.md rename to docs/legacy-docs/google-drive/vectorize-guide.md index 9a38b4c..4f3ac3a 100644 --- a/docs/google-drive/vectorize-guide.md +++ b/docs/legacy-docs/google-drive/vectorize-guide.md @@ -25,8 +25,8 @@ Add the following environment variables to your Next.js application: ```env # Vectorize credentials -VECTORIZE_ORG=your-organization-id -VECTORIZE_TOKEN=your-api-key +VECTORIZE_ORGANIZATION_ID=your-organization-id +VECTORIZE_API_KEY=your-api-key ``` ## Step 2 (Optional): Create Connector API Route @@ -53,8 +53,8 @@ export async function POST(request: Request) { // Gather environment variables for your Vectorize config const config: VectorizeAPIConfig = { - organizationId: process.env.VECTORIZE_ORG ?? "", - authorization: process.env.VECTORIZE_TOKEN ?? "", + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", }; // Validate environment variables @@ -92,8 +92,8 @@ import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { try { // Get authentication details from environment variables - const apiKey = process.env.VECTORIZE_TOKEN; - const organizationId = process.env.VECTORIZE_ORG; + const apiKey = process.env.VECTORIZE_API_KEY; + const organizationId = process.env.VECTORIZE_ORGANIZATION_ID; if (!apiKey || !organizationId) { return NextResponse.json({ @@ -186,8 +186,8 @@ export async function POST(request: NextRequest) { // Get Vectorize config const config: VectorizeAPIConfig = { - organizationId: process.env.VECTORIZE_ORG ?? "", - authorization: process.env.VECTORIZE_TOKEN ?? "", + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", }; // Get request body diff --git a/docs/google-drive/white-label-guide.md b/docs/legacy-docs/google-drive/white-label-guide.md similarity index 97% rename from docs/google-drive/white-label-guide.md rename to docs/legacy-docs/google-drive/white-label-guide.md index 650a729..90fef51 100644 --- a/docs/google-drive/white-label-guide.md +++ b/docs/legacy-docs/google-drive/white-label-guide.md @@ -39,8 +39,8 @@ Add the following environment variables to your Next.js application: ```env # Vectorize credentials -VECTORIZE_ORG=your-organization-id -VECTORIZE_TOKEN=your-api-key +VECTORIZE_ORGANIZATION_ID=your-organization-id +VECTORIZE_API_KEY=your-api-key # Google OAuth credentials GOOGLE_OAUTH_CLIENT_ID=your-client-id @@ -70,8 +70,8 @@ export async function POST(request: Request) { // Gather environment variables for your Vectorize config const config: VectorizeAPIConfig = { - organizationId: process.env.VECTORIZE_ORG ?? "", - authorization: process.env.VECTORIZE_TOKEN ?? "", + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", }; // Validate environment variables @@ -175,8 +175,8 @@ export async function POST(request: NextRequest) { // Configure Vectorize API const config: VectorizeAPIConfig = { - organizationId: process.env.VECTORIZE_ORG ?? "", - authorization: process.env.VECTORIZE_TOKEN ?? "", + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", }; // Manage the user diff --git a/docs/testing/vectorize/README.md b/docs/testing/vectorize/README.md new file mode 100644 index 0000000..52b3114 --- /dev/null +++ b/docs/testing/vectorize/README.md @@ -0,0 +1,347 @@ +# Testing - Vectorize Approach + +Testing strategies for Vectorize connectors. + +## Test Environment Setup + +```typescript +// jest.config.js +module.exports = { + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapping: { + '^@/(.*)$': '/src/$1', + }, +}; +``` + +```typescript +// src/setupTests.ts +import '@testing-library/jest-dom'; + +// Mock environment variables +process.env.VECTORIZE_API_KEY = 'test-api-key'; +process.env.VECTORIZE_ORGANIZATION_ID = 'test-org-id'; +``` + +## Unit Tests + +### Testing Connector Creation + +```typescript +// __tests__/connector.test.ts +import { + createVectorizeGDriveConnector, + createVectorizeDropboxConnector, + createVectorizeNotionConnector +} from '@vectorize-io/vectorize-connect'; + +describe('Vectorize Connector Creation', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should create a Google Drive connector successfully', async () => { + const connectorId = await createVectorizeGDriveConnector( + mockConfig, + 'Test Google Drive Connector' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should create a Dropbox connector successfully', async () => { + const connectorId = await createVectorizeDropboxConnector( + mockConfig, + 'Test Dropbox Connector' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should create a Notion connector successfully', async () => { + const connectorId = await createVectorizeNotionConnector( + mockConfig, + 'Test Notion Connector' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should handle invalid credentials', async () => { + const invalidConfig = { + authorization: '', + organizationId: '', + }; + + await expect( + createVectorizeGDriveConnector(invalidConfig, 'Test Connector') + ).rejects.toThrow(); + }); + + it('should handle Notion connector creation errors', async () => { + const invalidConfig = { + authorization: '', + organizationId: '', + }; + + await expect( + createVectorizeNotionConnector(invalidConfig, 'Test Connector') + ).rejects.toThrow(); + }); +}); +``` + +### Testing Token Generation + +```typescript +// __tests__/token.test.ts +import { getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +describe('One-Time Token Generation', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should generate a valid token', async () => { + const tokenResponse = await getOneTimeConnectorToken( + mockConfig, + 'user123', + 'connector123' + ); + + expect(tokenResponse.token).toBeDefined(); + expect(typeof tokenResponse.token).toBe('string'); + }); + + it('should handle missing parameters', async () => { + await expect( + getOneTimeConnectorToken(mockConfig, '', 'connector123') + ).rejects.toThrow(); + }); +}); +``` + +## Integration Tests + +### Testing Complete Flow + +```typescript +// __tests__/integration.test.ts +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import VectorizeConnector from '../components/VectorizeConnector'; + +// Mock the SDK functions +jest.mock('@vectorize-io/vectorize-connect', () => ({ + createVectorizeGDriveConnector: jest.fn().mockResolvedValue('test-gdrive-connector-id'), + createVectorizeDropboxConnector: jest.fn().mockResolvedValue('test-dropbox-connector-id'), + createVectorizeNotionConnector: jest.fn().mockResolvedValue('test-notion-connector-id'), + getOneTimeConnectorToken: jest.fn().mockResolvedValue({ token: 'test-token' }), + manageNotionUser: jest.fn().mockResolvedValue({ ok: true }), + PlatformOAuth: { + redirectToVectorizeConnect: jest.fn(), + }, +})); + +describe('VectorizeConnector Integration', () => { + it('should complete the full connector flow', async () => { + render(); + + // Create connector + const createButton = screen.getByText('Create Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText('Connector Created')).toBeInTheDocument(); + }); + + // Connect user + const connectButton = screen.getByText('Connect to Platform'); + fireEvent.click(connectButton); + + await waitFor(() => { + expect(screen.getByText('Connecting...')).toBeInTheDocument(); + }); + }); +}); +``` + +## API Route Tests + +### Testing Connector Creation API + +```typescript +// __tests__/api/createConnector.test.ts +import { POST } from '../../app/api/createConnector/route'; +import { NextRequest } from 'next/server'; + +describe('/api/createConnector', () => { + it('should create a connector successfully', async () => { + const request = new NextRequest('http://localhost:3000/api/createConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.connectorId).toBeDefined(); + }); + + it('should handle missing connector name', async () => { + const request = new NextRequest('http://localhost:3000/api/createConnector', { + method: 'POST', + body: JSON.stringify({}), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBeDefined(); + }); +}); +``` + +### Testing Token Generation API + +```typescript +// __tests__/api/token.test.ts +import { GET } from '../../app/api/get-one-time-connector-token/route'; +import { NextRequest } from 'next/server'; + +describe('/api/get-one-time-connector-token', () => { + it('should generate a token successfully', async () => { + const url = new URL('http://localhost:3000/api/get-one-time-connector-token'); + url.searchParams.set('userId', 'user123'); + url.searchParams.set('connectorId', 'connector123'); + + const request = new NextRequest(url); + const response = await GET(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.token).toBeDefined(); + }); +}); +``` + +## Manual Testing Checklist + +### Connector Creation +- [ ] Create connector with valid credentials +- [ ] Handle invalid credentials gracefully +- [ ] Verify connector appears in Vectorize dashboard + +### User Authentication +- [ ] Generate one-time token successfully +- [ ] Redirect to Vectorize authentication page +- [ ] Complete authentication flow +- [ ] Verify user is added to connector + +### User Management +- [ ] Edit user file selection +- [ ] Remove user from connector +- [ ] Handle user management errors + +### Error Scenarios +- [ ] Network connectivity issues +- [ ] Invalid API credentials +- [ ] Expired tokens +- [ ] Missing environment variables + +## Performance Testing + +```typescript +// __tests__/performance.test.ts +describe('Performance Tests', () => { + it('should create connectors within acceptable time', async () => { + const start = Date.now(); + + await createVectorizeGDriveConnector(mockConfig, 'Performance Test'); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(5000); // 5 seconds max + }); + + it('should handle concurrent token requests', async () => { + const promises = Array.from({ length: 10 }, (_, i) => + getOneTimeConnectorToken(mockConfig, `user${i}`, 'connector123') + ); + + const results = await Promise.all(promises); + + expect(results).toHaveLength(10); + results.forEach(result => { + expect(result.token).toBeDefined(); + }); + }); +}); +``` + +## Test Data Setup + +```typescript +// __tests__/helpers/testData.ts +export const mockVectorizeConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', +}; + +export const mockConnectorData = { + id: 'test-connector-id', + name: 'Test Connector', + type: 'PLATFORM_OAUTH_MULTI', +}; + +export const mockUserData = { + id: 'test-user-id', + name: 'Test User', + email: 'test@example.com', +}; + +export const mockTokenResponse = { + token: 'test-one-time-token', + expiresAt: new Date(Date.now() + 300000), // 5 minutes +}; +``` + +## Running Tests + +```bash +# Run all tests +npm test + +# Run tests in watch mode +npm test -- --watch + +# Run tests with coverage +npm test -- --coverage + +# Run specific test file +npm test -- connector.test.ts + +# Run integration tests only +npm test -- --testPathPattern=integration +``` + +## Platform-Specific Examples + +For detailed platform-specific testing examples: + +- [Google Drive Testing](./google-drive.md) +- [Dropbox Testing](./dropbox.md) +- [Notion Testing](./notion.md) + +## Next Steps + +- [Environment Setup](../../environment-setup/vectorize/) +- [Creating Connectors](../../creating-connectors/vectorize/) diff --git a/docs/testing/vectorize/dropbox.md b/docs/testing/vectorize/dropbox.md new file mode 100644 index 0000000..dba7260 --- /dev/null +++ b/docs/testing/vectorize/dropbox.md @@ -0,0 +1,319 @@ +# Dropbox Testing - Vectorize Approach + +Testing strategies for Dropbox Vectorize connectors. + +## Test Environment Setup + +```typescript +// jest.config.js +module.exports = { + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapping: { + '^@/(.*)$': '/src/$1', + }, +}; +``` + +```typescript +// src/setupTests.ts +import '@testing-library/jest-dom'; + +// Mock environment variables +process.env.VECTORIZE_API_KEY = 'test-api-key'; +process.env.VECTORIZE_ORGANIZATION_ID = 'test-org-id'; +``` + +## Unit Tests + +### Testing Dropbox Connector Creation + +```typescript +// __tests__/dropbox-vectorize.test.ts +import { createVectorizeDropboxConnector } from '@vectorize-io/vectorize-connect'; + +describe('Dropbox Vectorize Connector', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should create a Dropbox connector successfully', async () => { + const connectorId = await createVectorizeDropboxConnector( + mockConfig, + 'Test Dropbox Connector' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should handle invalid credentials', async () => { + const invalidConfig = { + authorization: '', + organizationId: '', + }; + + await expect( + createVectorizeDropboxConnector(invalidConfig, 'Test Connector') + ).rejects.toThrow(); + }); +}); +``` + +### Testing Dropbox Token Generation + +```typescript +// __tests__/dropbox-token.test.ts +import { getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +describe('Dropbox Token Generation', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should generate a valid token for Dropbox connector', async () => { + const tokenResponse = await getOneTimeConnectorToken( + mockConfig, + 'dropbox-user123', + 'dropbox-connector123' + ); + + expect(tokenResponse.token).toBeDefined(); + expect(typeof tokenResponse.token).toBe('string'); + }); + + it('should handle missing Dropbox user ID', async () => { + await expect( + getOneTimeConnectorToken(mockConfig, '', 'dropbox-connector123') + ).rejects.toThrow(); + }); +}); +``` + +## Integration Tests + +### Testing Complete Dropbox Flow + +```typescript +// __tests__/integration/dropbox-vectorize.test.ts +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import DropboxVectorizeConnector from '../components/DropboxVectorizeConnector'; + +// Mock the SDK functions +jest.mock('@vectorize-io/vectorize-connect', () => ({ + createVectorizeDropboxConnector: jest.fn().mockResolvedValue('test-dropbox-connector-id'), + getOneTimeConnectorToken: jest.fn().mockResolvedValue({ token: 'test-dropbox-token' }), + DropboxOAuth: { + redirectToVectorizeConnect: jest.fn(), + redirectToVectorizeEdit: jest.fn(), + }, +})); + +describe('DropboxVectorizeConnector Integration', () => { + it('should complete the full Dropbox connector flow', async () => { + render(); + + // Create Dropbox connector + const createButton = screen.getByText('Create Dropbox Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText('Dropbox Connector Created')).toBeInTheDocument(); + }); + + // Connect Dropbox user + const connectButton = screen.getByText('Connect with Dropbox'); + fireEvent.click(connectButton); + + await waitFor(() => { + expect(screen.getByText('Connecting to Dropbox...')).toBeInTheDocument(); + }); + }); + + it('should handle Dropbox connection errors', async () => { + const mockError = new Error('Dropbox connection failed'); + require('@vectorize-io/vectorize-connect').DropboxOAuth.redirectToVectorizeConnect.mockRejectedValue(mockError); + + render(); + + const connectButton = screen.getByText('Connect with Dropbox'); + fireEvent.click(connectButton); + + await waitFor(() => { + expect(screen.getByText('Dropbox connection failed')).toBeInTheDocument(); + }); + }); +}); +``` + +## API Route Tests + +### Testing Dropbox Connector API + +```typescript +// __tests__/api/dropbox-connector.test.ts +import { POST } from '../../app/api/createDropboxConnector/route'; +import { NextRequest } from 'next/server'; + +describe('/api/createDropboxConnector', () => { + it('should create a Dropbox connector successfully', async () => { + const request = new NextRequest('http://localhost:3000/api/createDropboxConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Dropbox Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.connectorId).toBeDefined(); + }); + + it('should handle missing connector name', async () => { + const request = new NextRequest('http://localhost:3000/api/createDropboxConnector', { + method: 'POST', + body: JSON.stringify({}), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBeDefined(); + }); +}); +``` + +### Testing Dropbox Token API + +```typescript +// __tests__/api/dropbox-token.test.ts +import { GET } from '../../app/api/get-dropbox-token/route'; +import { NextRequest } from 'next/server'; + +describe('/api/get-dropbox-token', () => { + it('should generate a Dropbox token successfully', async () => { + const url = new URL('http://localhost:3000/api/get-dropbox-token'); + url.searchParams.set('userId', 'dropbox-user123'); + url.searchParams.set('connectorId', 'dropbox-connector123'); + + const request = new NextRequest(url); + const response = await GET(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.token).toBeDefined(); + }); +}); +``` + +## User Management Tests + +### Testing Dropbox User Management + +```typescript +// __tests__/user-management/dropbox.test.ts +import { manageUser } from '@vectorize-io/vectorize-connect'; + +describe('Dropbox User Management', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should edit Dropbox user successfully', async () => { + const result = await manageUser( + mockConfig, + 'dropbox-connector123', + 'dropbox-user123', + 'edit', + { selectedFiles: ['file1', 'file2'] } + ); + + expect(result).toBeDefined(); + }); + + it('should remove Dropbox user successfully', async () => { + const result = await manageUser( + mockConfig, + 'dropbox-connector123', + 'dropbox-user123', + 'remove' + ); + + expect(result).toBeDefined(); + }); +}); +``` + +## Manual Testing Checklist + +### Dropbox Integration +- [ ] Create Dropbox connector with valid Vectorize credentials +- [ ] Generate one-time token for Dropbox user +- [ ] Redirect to Vectorize Dropbox authentication +- [ ] Complete Dropbox file selection +- [ ] Verify files are processed by Vectorize +- [ ] Test Dropbox file selection editing +- [ ] Test Dropbox user removal + +### Error Scenarios +- [ ] Invalid Vectorize API credentials +- [ ] Network connectivity issues +- [ ] Expired Dropbox tokens +- [ ] Missing environment variables +- [ ] Dropbox API rate limiting + +## Performance Testing + +```typescript +// __tests__/performance/dropbox.test.ts +describe('Dropbox Performance Tests', () => { + it('should create Dropbox connectors within acceptable time', async () => { + const start = Date.now(); + + await createVectorizeDropboxConnector(mockConfig, 'Performance Test'); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(5000); // 5 seconds max + }); + + it('should handle concurrent Dropbox token requests', async () => { + const promises = Array.from({ length: 10 }, (_, i) => + getOneTimeConnectorToken(mockConfig, `dropbox-user${i}`, 'dropbox-connector123') + ); + + const results = await Promise.all(promises); + + expect(results).toHaveLength(10); + results.forEach(result => { + expect(result.token).toBeDefined(); + }); + }); +}); +``` + +## Running Tests + +```bash +# Run Dropbox specific tests +npm test -- dropbox + +# Run Dropbox integration tests +npm test -- integration/dropbox + +# Run Dropbox API tests +npm test -- api/dropbox + +# Run with coverage +npm test -- --coverage dropbox +``` + +## Next Steps + +- [Dropbox User Management](../../user-management/vectorize/dropbox.md) +- [Dropbox Frontend Implementation](../../frontend-implementation/vectorize/dropbox.md) diff --git a/docs/testing/vectorize/google-drive.md b/docs/testing/vectorize/google-drive.md new file mode 100644 index 0000000..9425666 --- /dev/null +++ b/docs/testing/vectorize/google-drive.md @@ -0,0 +1,319 @@ +# Google Drive Testing - Vectorize Approach + +Testing strategies for Google Drive Vectorize connectors. + +## Test Environment Setup + +```typescript +// jest.config.js +module.exports = { + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapping: { + '^@/(.*)$': '/src/$1', + }, +}; +``` + +```typescript +// src/setupTests.ts +import '@testing-library/jest-dom'; + +// Mock environment variables +process.env.VECTORIZE_API_KEY = 'test-api-key'; +process.env.VECTORIZE_ORGANIZATION_ID = 'test-org-id'; +``` + +## Unit Tests + +### Testing Google Drive Connector Creation + +```typescript +// __tests__/google-drive-vectorize.test.ts +import { createVectorizeGDriveConnector } from '@vectorize-io/vectorize-connect'; + +describe('Google Drive Vectorize Connector', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should create a Google Drive connector successfully', async () => { + const connectorId = await createVectorizeGDriveConnector( + mockConfig, + 'Test Google Drive Connector' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should handle invalid credentials', async () => { + const invalidConfig = { + authorization: '', + organizationId: '', + }; + + await expect( + createVectorizeGDriveConnector(invalidConfig, 'Test Connector') + ).rejects.toThrow(); + }); +}); +``` + +### Testing Google Drive Token Generation + +```typescript +// __tests__/google-drive-token.test.ts +import { getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +describe('Google Drive Token Generation', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should generate a valid token for Google Drive connector', async () => { + const tokenResponse = await getOneTimeConnectorToken( + mockConfig, + 'gdrive-user123', + 'gdrive-connector123' + ); + + expect(tokenResponse.token).toBeDefined(); + expect(typeof tokenResponse.token).toBe('string'); + }); + + it('should handle missing Google Drive user ID', async () => { + await expect( + getOneTimeConnectorToken(mockConfig, '', 'gdrive-connector123') + ).rejects.toThrow(); + }); +}); +``` + +## Integration Tests + +### Testing Complete Google Drive Flow + +```typescript +// __tests__/integration/google-drive-vectorize.test.ts +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import GoogleDriveVectorizeConnector from '../components/GoogleDriveVectorizeConnector'; + +// Mock the SDK functions +jest.mock('@vectorize-io/vectorize-connect', () => ({ + createVectorizeGDriveConnector: jest.fn().mockResolvedValue('test-gdrive-connector-id'), + getOneTimeConnectorToken: jest.fn().mockResolvedValue({ token: 'test-gdrive-token' }), + GoogleDriveOAuth: { + redirectToVectorizeConnect: jest.fn(), + redirectToVectorizeEdit: jest.fn(), + }, +})); + +describe('GoogleDriveVectorizeConnector Integration', () => { + it('should complete the full Google Drive connector flow', async () => { + render(); + + // Create Google Drive connector + const createButton = screen.getByText('Create Google Drive Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText('Google Drive Connector Created')).toBeInTheDocument(); + }); + + // Connect Google Drive user + const connectButton = screen.getByText('Connect with Google Drive'); + fireEvent.click(connectButton); + + await waitFor(() => { + expect(screen.getByText('Connecting to Google Drive...')).toBeInTheDocument(); + }); + }); + + it('should handle Google Drive connection errors', async () => { + const mockError = new Error('Google Drive connection failed'); + require('@vectorize-io/vectorize-connect').GoogleDriveOAuth.redirectToVectorizeConnect.mockRejectedValue(mockError); + + render(); + + const connectButton = screen.getByText('Connect with Google Drive'); + fireEvent.click(connectButton); + + await waitFor(() => { + expect(screen.getByText('Google Drive connection failed')).toBeInTheDocument(); + }); + }); +}); +``` + +## API Route Tests + +### Testing Google Drive Connector API + +```typescript +// __tests__/api/google-drive-connector.test.ts +import { POST } from '../../app/api/createGDriveConnector/route'; +import { NextRequest } from 'next/server'; + +describe('/api/createGDriveConnector', () => { + it('should create a Google Drive connector successfully', async () => { + const request = new NextRequest('http://localhost:3000/api/createGDriveConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Google Drive Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.connectorId).toBeDefined(); + }); + + it('should handle missing connector name', async () => { + const request = new NextRequest('http://localhost:3000/api/createGDriveConnector', { + method: 'POST', + body: JSON.stringify({}), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBeDefined(); + }); +}); +``` + +### Testing Google Drive Token API + +```typescript +// __tests__/api/google-drive-token.test.ts +import { GET } from '../../app/api/get-gdrive-token/route'; +import { NextRequest } from 'next/server'; + +describe('/api/get-gdrive-token', () => { + it('should generate a Google Drive token successfully', async () => { + const url = new URL('http://localhost:3000/api/get-gdrive-token'); + url.searchParams.set('userId', 'gdrive-user123'); + url.searchParams.set('connectorId', 'gdrive-connector123'); + + const request = new NextRequest(url); + const response = await GET(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.token).toBeDefined(); + }); +}); +``` + +## User Management Tests + +### Testing Google Drive User Management + +```typescript +// __tests__/user-management/google-drive.test.ts +import { manageUser } from '@vectorize-io/vectorize-connect'; + +describe('Google Drive User Management', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should edit Google Drive user successfully', async () => { + const result = await manageUser( + mockConfig, + 'gdrive-connector123', + 'gdrive-user123', + 'edit', + { selectedFiles: ['file1', 'file2'] } + ); + + expect(result).toBeDefined(); + }); + + it('should remove Google Drive user successfully', async () => { + const result = await manageUser( + mockConfig, + 'gdrive-connector123', + 'gdrive-user123', + 'remove' + ); + + expect(result).toBeDefined(); + }); +}); +``` + +## Manual Testing Checklist + +### Google Drive Integration +- [ ] Create Google Drive connector with valid Vectorize credentials +- [ ] Generate one-time token for Google Drive user +- [ ] Redirect to Vectorize Google Drive authentication +- [ ] Complete Google Drive file selection +- [ ] Verify files are processed by Vectorize +- [ ] Test Google Drive file selection editing +- [ ] Test Google Drive user removal + +### Error Scenarios +- [ ] Invalid Vectorize API credentials +- [ ] Network connectivity issues +- [ ] Expired Google Drive tokens +- [ ] Missing environment variables +- [ ] Google Drive API rate limiting + +## Performance Testing + +```typescript +// __tests__/performance/google-drive.test.ts +describe('Google Drive Performance Tests', () => { + it('should create Google Drive connectors within acceptable time', async () => { + const start = Date.now(); + + await createVectorizeGDriveConnector(mockConfig, 'Performance Test'); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(5000); // 5 seconds max + }); + + it('should handle concurrent Google Drive token requests', async () => { + const promises = Array.from({ length: 10 }, (_, i) => + getOneTimeConnectorToken(mockConfig, `gdrive-user${i}`, 'gdrive-connector123') + ); + + const results = await Promise.all(promises); + + expect(results).toHaveLength(10); + results.forEach(result => { + expect(result.token).toBeDefined(); + }); + }); +}); +``` + +## Running Tests + +```bash +# Run Google Drive specific tests +npm test -- google-drive + +# Run Google Drive integration tests +npm test -- integration/google-drive + +# Run Google Drive API tests +npm test -- api/google-drive + +# Run with coverage +npm test -- --coverage google-drive +``` + +## Next Steps + +- [Google Drive User Management](../../user-management/vectorize/google-drive.md) +- [Google Drive Frontend Implementation](../../frontend-implementation/vectorize/google-drive.md) diff --git a/docs/testing/vectorize/notion.md b/docs/testing/vectorize/notion.md new file mode 100644 index 0000000..96028c5 --- /dev/null +++ b/docs/testing/vectorize/notion.md @@ -0,0 +1,342 @@ +# Notion Testing - Vectorize Approach + +Testing strategies for Notion Vectorize connectors. + +## Test Environment Setup + +```typescript +// jest.config.js +module.exports = { + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapping: { + '^@/(.*)$': '/src/$1', + }, +}; +``` + +```typescript +// src/setupTests.ts +import '@testing-library/jest-dom'; + +// Mock environment variables +process.env.VECTORIZE_API_KEY = 'test-api-key'; +process.env.VECTORIZE_ORGANIZATION_ID = 'test-org-id'; +``` + +## Unit Tests + +### Testing Notion Connector Creation + +```typescript +// __tests__/notion-vectorize.test.ts +import { createVectorizeNotionConnector } from '@vectorize-io/vectorize-connect'; + +describe('Notion Vectorize Connector', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should create a Notion connector successfully', async () => { + const connectorId = await createVectorizeNotionConnector( + mockConfig, + 'Test Notion Connector' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should handle invalid credentials', async () => { + const invalidConfig = { + authorization: '', + organizationId: '', + }; + + await expect( + createVectorizeNotionConnector(invalidConfig, 'Test Connector') + ).rejects.toThrow(); + }); +}); +``` + +### Testing Notion Token Generation + +```typescript +// __tests__/notion-token.test.ts +import { getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +describe('Notion Token Generation', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should generate a valid token for Notion connector', async () => { + const tokenResponse = await getOneTimeConnectorToken( + mockConfig, + 'notion-user123', + 'notion-connector123' + ); + + expect(tokenResponse.token).toBeDefined(); + expect(typeof tokenResponse.token).toBe('string'); + }); + + it('should handle missing Notion user ID', async () => { + await expect( + getOneTimeConnectorToken(mockConfig, '', 'notion-connector123') + ).rejects.toThrow(); + }); +}); +``` + +## Integration Tests + +### Testing Complete Notion Flow + +```typescript +// __tests__/integration/notion-vectorize.test.ts +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import NotionVectorizeConnector from '../components/NotionVectorizeConnector'; + +// Mock the SDK functions +jest.mock('@vectorize-io/vectorize-connect', () => ({ + createVectorizeNotionConnector: jest.fn().mockResolvedValue('test-notion-connector-id'), + getOneTimeConnectorToken: jest.fn().mockResolvedValue({ token: 'test-notion-token' }), + manageNotionUser: jest.fn().mockResolvedValue({ ok: true }), + NotionOAuth: { + redirectToVectorizeConnect: jest.fn(), + redirectToVectorizeEdit: jest.fn(), + }, +})); + +describe('NotionVectorizeConnector Integration', () => { + it('should complete the full Notion connector flow', async () => { + render(); + + // Create Notion connector + const createButton = screen.getByText('Create Notion Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText('Notion Connector Created')).toBeInTheDocument(); + }); + + // Connect Notion user + const connectButton = screen.getByText('Connect with Notion'); + fireEvent.click(connectButton); + + await waitFor(() => { + expect(screen.getByText('Connecting to Notion...')).toBeInTheDocument(); + }); + }); + + it('should handle Notion connection errors', async () => { + const mockError = new Error('Notion connection failed'); + require('@vectorize-io/vectorize-connect').NotionOAuth.redirectToVectorizeConnect.mockRejectedValue(mockError); + + render(); + + const connectButton = screen.getByText('Connect with Notion'); + fireEvent.click(connectButton); + + await waitFor(() => { + expect(screen.getByText('Notion connection failed')).toBeInTheDocument(); + }); + }); +}); +``` + +## API Route Tests + +### Testing Notion Connector API + +```typescript +// __tests__/api/notion-connector.test.ts +import { POST } from '../../app/api/createNotionConnector/route'; +import { NextRequest } from 'next/server'; + +describe('/api/createNotionConnector', () => { + it('should create a Notion connector successfully', async () => { + const request = new NextRequest('http://localhost:3000/api/createNotionConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Notion Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.connectorId).toBeDefined(); + }); + + it('should handle missing connector name', async () => { + const request = new NextRequest('http://localhost:3000/api/createNotionConnector', { + method: 'POST', + body: JSON.stringify({}), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBeDefined(); + }); +}); +``` + +### Testing Notion Token API + +```typescript +// __tests__/api/notion-token.test.ts +import { GET } from '../../app/api/get-notion-token/route'; +import { NextRequest } from 'next/server'; + +describe('/api/get-notion-token', () => { + it('should generate a Notion token successfully', async () => { + const url = new URL('http://localhost:3000/api/get-notion-token'); + url.searchParams.set('userId', 'notion-user123'); + url.searchParams.set('connectorId', 'notion-connector123'); + + const request = new NextRequest(url); + const response = await GET(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.token).toBeDefined(); + }); +}); +``` + +## User Management Tests + +### Testing Notion User Management + +```typescript +// __tests__/user-management/notion.test.ts +import { manageNotionUser } from '@vectorize-io/vectorize-connect'; + +describe('Notion User Management', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + const mockSelectedPages = { + 'page1': { title: 'Test Page 1', pageId: 'page1' }, + 'page2': { title: 'Test Page 2', pageId: 'page2' } + }; + + it('should add Notion user successfully', async () => { + const result = await manageNotionUser( + mockConfig, + 'notion-connector123', + mockSelectedPages, + 'notion-access-token', + 'notion-user123', + 'add' + ); + + expect(result).toBeDefined(); + }); + + it('should edit Notion user successfully', async () => { + const result = await manageNotionUser( + mockConfig, + 'notion-connector123', + mockSelectedPages, + 'notion-access-token', + 'notion-user123', + 'edit' + ); + + expect(result).toBeDefined(); + }); + + it('should remove Notion user successfully', async () => { + const result = await manageNotionUser( + mockConfig, + 'notion-connector123', + null, + '', + 'notion-user123', + 'remove' + ); + + expect(result).toBeDefined(); + }); +}); +``` + +## Manual Testing Checklist + +### Notion Integration +- [ ] Create Notion connector with valid Vectorize credentials +- [ ] Generate one-time token for Notion user +- [ ] Redirect to Vectorize Notion authentication +- [ ] Complete Notion page selection +- [ ] Verify pages are processed by Vectorize +- [ ] Test Notion page selection editing +- [ ] Test Notion user removal + +### Error Scenarios +- [ ] Invalid Vectorize API credentials +- [ ] Network connectivity issues +- [ ] Expired Notion tokens +- [ ] Missing environment variables +- [ ] Notion API rate limiting +- [ ] Invalid Notion workspace permissions + +## Performance Testing + +```typescript +// __tests__/performance/notion.test.ts +describe('Notion Performance Tests', () => { + it('should create Notion connectors within acceptable time', async () => { + const start = Date.now(); + + await createVectorizeNotionConnector(mockConfig, 'Performance Test'); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(5000); // 5 seconds max + }); + + it('should handle concurrent Notion token requests', async () => { + const promises = Array.from({ length: 10 }, (_, i) => + getOneTimeConnectorToken(mockConfig, `notion-user${i}`, 'notion-connector123') + ); + + const results = await Promise.all(promises); + + expect(results).toHaveLength(10); + results.forEach(result => { + expect(result.token).toBeDefined(); + }); + }); +}); +``` + +## Running Tests + +```bash +# Run Notion specific tests +npm test -- notion + +# Run Notion integration tests +npm test -- integration/notion + +# Run Notion API tests +npm test -- api/notion + +# Run with coverage +npm test -- --coverage notion +``` + +## Next Steps + +- [Notion User Management](../../user-management/vectorize/notion.md) +- [Notion Frontend Implementation](../../frontend-implementation/vectorize/notion.md) diff --git a/docs/testing/white-label/README.md b/docs/testing/white-label/README.md new file mode 100644 index 0000000..feee325 --- /dev/null +++ b/docs/testing/white-label/README.md @@ -0,0 +1,422 @@ +# Testing - White-Label Approach + +Testing strategies for white-label connectors with custom OAuth flows. + +## Test Environment Setup + +```typescript +// jest.config.js +module.exports = { + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapping: { + '^@/(.*)$': '/src/$1', + }, +}; +``` + +```typescript +// src/setupTests.ts +import '@testing-library/jest-dom'; + +// Mock environment variables +process.env.VECTORIZE_API_KEY = 'test-api-key'; +process.env.VECTORIZE_ORGANIZATION_ID = 'test-org-id'; +process.env.GOOGLE_OAUTH_CLIENT_ID = 'test-google-client-id'; +process.env.GOOGLE_OAUTH_CLIENT_SECRET = 'test-google-client-secret'; +process.env.GOOGLE_API_KEY = 'test-google-api-key'; +process.env.DROPBOX_APP_KEY = 'test-dropbox-app-key'; +process.env.DROPBOX_APP_SECRET = 'test-dropbox-app-secret'; +``` + +## Unit Tests + +### Testing Google Drive Connector Creation + +```typescript +// __tests__/google-drive.test.ts +import { createWhiteLabelGDriveConnector } from '@vectorize-io/vectorize-connect'; + +describe('Google Drive White-Label Connector', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should create a Google Drive connector successfully', async () => { + const connectorId = await createWhiteLabelGDriveConnector( + mockConfig, + 'Test Google Drive Connector', + 'test-client-id', + 'test-client-secret' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should handle missing OAuth credentials', async () => { + await expect( + createWhiteLabelGDriveConnector( + mockConfig, + 'Test Connector', + '', + '' + ) + ).rejects.toThrow(); + }); +}); +``` + +### Testing Dropbox Connector Creation + +```typescript +// __tests__/dropbox.test.ts +import { createWhiteLabelDropboxConnector } from '@vectorize-io/vectorize-connect'; + +describe('Dropbox White-Label Connector', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should create a Dropbox connector successfully', async () => { + const connectorId = await createWhiteLabelDropboxConnector( + mockConfig, + 'Test Dropbox Connector', + 'test-app-key', + 'test-app-secret' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); +}); +``` + +## OAuth Flow Tests + +### Testing Google Drive OAuth + +```typescript +// __tests__/oauth/google-drive.test.ts +import { GoogleDriveOAuth } from '@vectorize-io/vectorize-connect'; + +// Mock window.open for popup testing +global.open = jest.fn(); + +describe('Google Drive OAuth Flow', () => { + const mockConfig = { + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + apiKey: 'test-api-key', + redirectUri: 'http://localhost:3000/api/oauth/callback', + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: jest.fn(), + onError: jest.fn(), + }; + + it('should start OAuth flow', () => { + GoogleDriveOAuth.startOAuth(mockConfig); + + expect(global.open).toHaveBeenCalled(); + }); + + it('should handle OAuth success', () => { + const mockResponse = { + fileIds: ['file1', 'file2'], + refreshToken: 'test-refresh-token', + }; + + mockConfig.onSuccess(mockResponse); + + expect(mockConfig.onSuccess).toHaveBeenCalledWith(mockResponse); + }); + + it('should handle OAuth error', () => { + const mockError = new Error('OAuth failed'); + + mockConfig.onError(mockError); + + expect(mockConfig.onError).toHaveBeenCalledWith(mockError); + }); +}); +``` + +### Testing Dropbox OAuth + +```typescript +// __tests__/oauth/dropbox.test.ts +import { DropboxOAuth } from '@vectorize-io/vectorize-connect'; + +describe('Dropbox OAuth Flow', () => { + const mockConfig = { + appKey: 'test-app-key', + appSecret: 'test-app-secret', + redirectUri: 'http://localhost:3000/api/dropbox-callback', + scopes: ['files.metadata.read', 'files.content.read'], + onSuccess: jest.fn(), + onError: jest.fn(), + }; + + it('should start OAuth flow', () => { + DropboxOAuth.startOAuth(mockConfig); + + expect(global.open).toHaveBeenCalled(); + }); +}); +``` + +## Integration Tests + +### Testing Complete Google Drive Flow + +```typescript +// __tests__/integration/google-drive.test.ts +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import GoogleDriveConnector from '../components/GoogleDriveConnector'; + +jest.mock('@vectorize-io/vectorize-connect', () => ({ + createWhiteLabelGDriveConnector: jest.fn().mockResolvedValue('test-connector-id'), + GoogleDriveOAuth: { + startOAuth: jest.fn(), + }, +})); + +describe('GoogleDriveConnector Integration', () => { + it('should complete the full connector flow', async () => { + render(); + + // Create connector + const createButton = screen.getByText('Create Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText('Connector Created')).toBeInTheDocument(); + }); + + // Start OAuth flow + const connectButton = screen.getByText('Connect with Google Drive'); + fireEvent.click(connectButton); + + expect(require('@vectorize-io/vectorize-connect').GoogleDriveOAuth.startOAuth).toHaveBeenCalled(); + }); +}); +``` + +## API Route Tests + +### Testing Google Drive Connector API + +```typescript +// __tests__/api/google-drive.test.ts +import { POST } from '../../app/api/createGDriveConnector/route'; +import { NextRequest } from 'next/server'; + +describe('/api/createGDriveConnector', () => { + it('should create a Google Drive connector successfully', async () => { + const request = new NextRequest('http://localhost:3000/api/createGDriveConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Google Drive Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.connectorId).toBeDefined(); + }); + + it('should handle missing environment variables', async () => { + // Temporarily remove environment variables + const originalClientId = process.env.GOOGLE_OAUTH_CLIENT_ID; + delete process.env.GOOGLE_OAUTH_CLIENT_ID; + + const request = new NextRequest('http://localhost:3000/api/createGDriveConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(500); + expect(data.error).toContain('Missing Google OAuth credentials'); + + // Restore environment variable + process.env.GOOGLE_OAUTH_CLIENT_ID = originalClientId; + }); +}); +``` + +### Testing OAuth Callback API + +```typescript +// __tests__/api/oauth-callback.test.ts +import { GET } from '../../app/api/oauth/callback/route'; +import { NextRequest } from 'next/server'; + +describe('/api/oauth/callback', () => { + it('should handle successful OAuth callback', async () => { + const url = new URL('http://localhost:3000/api/oauth/callback'); + url.searchParams.set('code', 'test-auth-code'); + + const request = new NextRequest(url); + const response = await GET(request); + + expect(response.status).toBe(200); + }); + + it('should handle OAuth error callback', async () => { + const url = new URL('http://localhost:3000/api/oauth/callback'); + url.searchParams.set('error', 'access_denied'); + + const request = new NextRequest(url); + const response = await GET(request); + + expect(response.status).toBe(500); + }); +}); +``` + +## User Management Tests + +### Testing Google Drive User Management + +```typescript +// __tests__/user-management/google-drive.test.ts +import { manageGDriveUser } from '@vectorize-io/vectorize-connect'; + +describe('Google Drive User Management', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should add user successfully', async () => { + const result = await manageGDriveUser( + mockConfig, + 'connector123', + ['file1', 'file2'], + 'refresh-token', + 'user123', + 'add' + ); + + expect(result).toBeDefined(); + }); + + it('should remove user successfully', async () => { + const result = await manageGDriveUser( + mockConfig, + 'connector123', + [], + '', + 'user123', + 'remove' + ); + + expect(result).toBeDefined(); + }); +}); +``` + +## Manual Testing Checklist + +### Google Drive Integration +- [ ] Create Google Drive connector with valid OAuth credentials +- [ ] Start OAuth flow and complete authentication +- [ ] Select files using Google Picker +- [ ] Verify files are processed by Vectorize +- [ ] Test file selection editing +- [ ] Test user removal + +### Dropbox Integration +- [ ] Create Dropbox connector with valid app credentials +- [ ] Start OAuth flow and complete authentication +- [ ] Select files using Dropbox chooser +- [ ] Verify files are processed by Vectorize +- [ ] Test user management operations + +### Error Scenarios +- [ ] Invalid OAuth credentials +- [ ] Network connectivity issues +- [ ] Popup blocked by browser +- [ ] OAuth flow cancellation +- [ ] File access permission issues + +## Performance Testing + +```typescript +// __tests__/performance/white-label.test.ts +describe('White-Label Performance Tests', () => { + it('should create connectors within acceptable time', async () => { + const start = Date.now(); + + await createWhiteLabelGDriveConnector( + mockConfig, + 'Performance Test', + 'client-id', + 'client-secret' + ); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(5000); + }); + + it('should handle multiple OAuth flows concurrently', async () => { + const promises = Array.from({ length: 5 }, (_, i) => + createWhiteLabelGDriveConnector( + mockConfig, + `Connector ${i}`, + 'client-id', + 'client-secret' + ) + ); + + const results = await Promise.all(promises); + + expect(results).toHaveLength(5); + results.forEach(result => { + expect(result).toBeDefined(); + }); + }); +}); +``` + +## Running Tests + +```bash +# Run all tests +npm test + +# Run platform-specific tests +npm test -- google-drive +npm test -- dropbox + +# Run OAuth flow tests +npm test -- oauth + +# Run integration tests +npm test -- integration + +# Run with coverage +npm test -- --coverage +``` + +## Platform-Specific Examples + +For detailed platform-specific testing examples: + +- [Google Drive Testing](./google-drive.md) +- [Dropbox Testing](./dropbox.md) +- [Notion Testing](./notion.md) + +## Next Steps + +- [Environment Setup](../../environment-setup/white-label/) +- [Creating Connectors](../../creating-connectors/white-label/) diff --git a/docs/testing/white-label/dropbox.md b/docs/testing/white-label/dropbox.md new file mode 100644 index 0000000..ea24673 --- /dev/null +++ b/docs/testing/white-label/dropbox.md @@ -0,0 +1,484 @@ +# Dropbox Testing - White-Label Approach + +Testing strategies for Dropbox White-Label connectors. + +## Test Environment Setup + +```typescript +// jest.config.js +module.exports = { + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapping: { + '^@/(.*)$': '/src/$1', + }, +}; +``` + +```typescript +// src/setupTests.ts +import '@testing-library/jest-dom'; + +// Mock environment variables +process.env.VECTORIZE_API_KEY = 'test-api-key'; +process.env.VECTORIZE_ORGANIZATION_ID = 'test-org-id'; +process.env.DROPBOX_APP_KEY = 'test-dropbox-app-key'; +process.env.DROPBOX_APP_SECRET = 'test-dropbox-app-secret'; +``` + +## Unit Tests + +### Testing Dropbox Connector Creation + +```typescript +// __tests__/dropbox-white-label.test.ts +import { createWhiteLabelDropboxConnector } from '@vectorize-io/vectorize-connect'; + +describe('Dropbox White-Label Connector', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should create a Dropbox connector successfully', async () => { + const connectorId = await createWhiteLabelDropboxConnector( + mockConfig, + 'Test Dropbox Connector', + 'test-app-key', + 'test-app-secret' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should handle missing OAuth credentials', async () => { + await expect( + createWhiteLabelDropboxConnector( + mockConfig, + 'Test Connector', + '', + '' + ) + ).rejects.toThrow(); + }); + + it('should handle invalid API credentials', async () => { + const invalidConfig = { + authorization: '', + organizationId: '', + }; + + await expect( + createWhiteLabelDropboxConnector( + invalidConfig, + 'Test Connector', + 'test-app-key', + 'test-app-secret' + ) + ).rejects.toThrow(); + }); +}); +``` + +### Testing OAuth Flow + +```typescript +// __tests__/oauth/dropbox-white-label.test.ts +import { DropboxOAuth } from '@vectorize-io/vectorize-connect'; + +// Mock window.open for popup testing +global.open = jest.fn(); + +describe('Dropbox White-Label OAuth Flow', () => { + const mockConfig = { + appKey: 'test-app-key', + appSecret: 'test-app-secret', + redirectUri: 'http://localhost:3000/api/dropbox-callback', + scopes: ['files.metadata.read', 'files.content.read'], + onSuccess: jest.fn(), + onError: jest.fn(), + }; + + it('should start OAuth flow', () => { + DropboxOAuth.startOAuth(mockConfig); + + expect(global.open).toHaveBeenCalled(); + }); + + it('should handle OAuth success', () => { + const mockResponse = { + selectedFiles: ['file1', 'file2'], + accessToken: 'test-access-token', + }; + + mockConfig.onSuccess(mockResponse); + + expect(mockConfig.onSuccess).toHaveBeenCalledWith(mockResponse); + }); + + it('should handle OAuth error', () => { + const mockError = new Error('OAuth failed'); + + mockConfig.onError(mockError); + + expect(mockConfig.onError).toHaveBeenCalledWith(mockError); + }); + + it('should validate required OAuth parameters', () => { + const invalidConfig = { + ...mockConfig, + appKey: '', + }; + + expect(() => DropboxOAuth.startOAuth(invalidConfig)).toThrow(); + }); +}); +``` + +## Integration Tests + +### Testing Complete Dropbox Flow + +```typescript +// __tests__/integration/dropbox-white-label.test.ts +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import DropboxWhiteLabel from '../components/DropboxWhiteLabel'; + +jest.mock('@vectorize-io/vectorize-connect', () => ({ + createWhiteLabelDropboxConnector: jest.fn().mockResolvedValue('test-connector-id'), + DropboxOAuth: { + startOAuth: jest.fn(), + }, + manageUser: jest.fn().mockResolvedValue({ ok: true }), +})); + +describe('DropboxWhiteLabel Integration', () => { + it('should complete the full connector flow', async () => { + render(); + + // Create connector + const createButton = screen.getByText('Create Dropbox Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText('Connector Created')).toBeInTheDocument(); + }); + + // Start OAuth flow + const connectButton = screen.getByText('Connect with Dropbox'); + fireEvent.click(connectButton); + + expect(require('@vectorize-io/vectorize-connect').DropboxOAuth.startOAuth).toHaveBeenCalled(); + }); + + it('should handle connector creation errors', async () => { + const mockError = new Error('Failed to create connector'); + require('@vectorize-io/vectorize-connect').createWhiteLabelDropboxConnector.mockRejectedValueOnce(mockError); + + render(); + + const createButton = screen.getByText('Create Dropbox Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText(/Failed to create connector/)).toBeInTheDocument(); + }); + }); +}); +``` + +## API Route Tests + +### Testing Dropbox Connector API + +```typescript +// __tests__/api/dropbox-white-label.test.ts +import { POST } from '../../app/api/createDropboxWhiteLabelConnector/route'; +import { NextRequest } from 'next/server'; + +describe('/api/createDropboxWhiteLabelConnector', () => { + it('should create a Dropbox connector successfully', async () => { + const request = new NextRequest('http://localhost:3000/api/createDropboxWhiteLabelConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Dropbox Connector', + appKey: 'test-app-key', + appSecret: 'test-app-secret', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.connectorId).toBeDefined(); + }); + + it('should handle missing OAuth credentials', async () => { + const request = new NextRequest('http://localhost:3000/api/createDropboxWhiteLabelConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toContain('Missing OAuth credentials'); + }); + + it('should handle missing environment variables', async () => { + // Temporarily remove environment variables + const originalApiKey = process.env.VECTORIZE_API_KEY; + delete process.env.VECTORIZE_API_KEY; + + const request = new NextRequest('http://localhost:3000/api/createDropboxWhiteLabelConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Connector', + appKey: 'test-app-key', + appSecret: 'test-app-secret', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(500); + expect(data.error).toContain('Missing Vectorize credentials'); + + // Restore environment variable + process.env.VECTORIZE_API_KEY = originalApiKey; + }); +}); +``` + +### Testing OAuth Callback API + +```typescript +// __tests__/api/oauth-callback-dropbox.test.ts +import { GET } from '../../app/api/oauth/dropbox-callback/route'; +import { NextRequest } from 'next/server'; + +describe('/api/oauth/dropbox-callback', () => { + it('should handle successful OAuth callback', async () => { + const url = new URL('http://localhost:3000/api/oauth/dropbox-callback'); + url.searchParams.set('code', 'test-auth-code'); + url.searchParams.set('state', 'test-state'); + + const request = new NextRequest(url); + const response = await GET(request); + + expect(response.status).toBe(200); + }); + + it('should handle OAuth error callback', async () => { + const url = new URL('http://localhost:3000/api/oauth/dropbox-callback'); + url.searchParams.set('error', 'access_denied'); + + const request = new NextRequest(url); + const response = await GET(request); + + expect(response.status).toBe(500); + }); + + it('should handle missing authorization code', async () => { + const url = new URL('http://localhost:3000/api/oauth/dropbox-callback'); + + const request = new NextRequest(url); + const response = await GET(request); + + expect(response.status).toBe(400); + }); +}); +``` + +## User Management Tests + +### Testing Dropbox User Management + +```typescript +// __tests__/user-management/dropbox-white-label.test.ts +import { manageUser } from '@vectorize-io/vectorize-connect'; + +describe('Dropbox White-Label User Management', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should add user successfully', async () => { + const result = await manageUser( + mockConfig, + 'connector123', + 'user123', + 'add', + { + selectedFiles: ['file1', 'file2'], + accessToken: 'test-access-token' + } + ); + + expect(result).toBeDefined(); + }); + + it('should edit user successfully', async () => { + const result = await manageUser( + mockConfig, + 'connector123', + 'user123', + 'edit', + { + selectedFiles: ['file1', 'file3'], + accessToken: 'test-access-token' + } + ); + + expect(result).toBeDefined(); + }); + + it('should remove user successfully', async () => { + const result = await manageUser( + mockConfig, + 'connector123', + 'user123', + 'remove' + ); + + expect(result).toBeDefined(); + }); + + it('should handle missing access token for add operation', async () => { + await expect( + manageUser( + mockConfig, + 'connector123', + 'user123', + 'add', + { + selectedFiles: ['file1', 'file2'] + } + ) + ).rejects.toThrow(); + }); +}); +``` + +## Manual Testing Checklist + +### Dropbox White-Label Integration +- [ ] Create Dropbox connector with valid OAuth credentials +- [ ] Start OAuth flow and complete authentication +- [ ] Select files using Dropbox chooser +- [ ] Verify files are processed by Vectorize +- [ ] Test file selection editing +- [ ] Test user removal +- [ ] Verify OAuth credentials validation + +### Error Scenarios +- [ ] Invalid OAuth credentials (app key/secret) +- [ ] Missing environment variables +- [ ] Network connectivity issues +- [ ] Popup blocked by browser +- [ ] OAuth flow cancellation +- [ ] File access permission issues +- [ ] Invalid redirect URI configuration + +### Security Testing +- [ ] OAuth state parameter validation +- [ ] CSRF protection +- [ ] Token expiration handling +- [ ] Secure credential storage + +## Performance Testing + +```typescript +// __tests__/performance/dropbox-white-label.test.ts +describe('Dropbox White-Label Performance Tests', () => { + it('should create connectors within acceptable time', async () => { + const start = Date.now(); + + await createWhiteLabelDropboxConnector( + mockConfig, + 'Performance Test', + 'app-key', + 'app-secret' + ); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(5000); + }); + + it('should handle multiple OAuth flows concurrently', async () => { + const promises = Array.from({ length: 5 }, (_, i) => + createWhiteLabelDropboxConnector( + mockConfig, + `Connector ${i}`, + 'app-key', + 'app-secret' + ) + ); + + const results = await Promise.all(promises); + + expect(results).toHaveLength(5); + results.forEach(result => { + expect(result).toBeDefined(); + }); + }); + + it('should handle large file selections efficiently', async () => { + const largeFileSelection = Array.from({ length: 100 }, (_, i) => ({ + id: `file_${i}`, + name: `File ${i}.pdf`, + size: 1024 * 1024 // 1MB + })); + + const start = Date.now(); + + await manageUser( + mockConfig, + 'connector123', + 'user123', + 'add', + { + selectedFiles: largeFileSelection, + accessToken: 'test-access-token' + } + ); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(10000); // 10 seconds max + }); +}); +``` + +## Running Tests + +```bash +# Run all tests +npm test + +# Run Dropbox specific tests +npm test -- dropbox + +# Run OAuth flow tests +npm test -- oauth + +# Run integration tests +npm test -- integration + +# Run with coverage +npm test -- --coverage + +# Run performance tests +npm test -- performance +``` + +## Next Steps + +- [Dropbox User Management](../../user-management/white-label/dropbox.md) +- [Environment Setup](../../environment-setup/white-label/) diff --git a/docs/testing/white-label/google-drive.md b/docs/testing/white-label/google-drive.md new file mode 100644 index 0000000..ae9728f --- /dev/null +++ b/docs/testing/white-label/google-drive.md @@ -0,0 +1,484 @@ +# Google Drive Testing - White-Label Approach + +Testing strategies for Google Drive White-Label connectors. + +## Test Environment Setup + +```typescript +// jest.config.js +module.exports = { + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapping: { + '^@/(.*)$': '/src/$1', + }, +}; +``` + +```typescript +// src/setupTests.ts +import '@testing-library/jest-dom'; + +// Mock environment variables +process.env.VECTORIZE_API_KEY = 'test-api-key'; +process.env.VECTORIZE_ORGANIZATION_ID = 'test-org-id'; +process.env.GOOGLE_CLIENT_ID = 'test-google-client-id'; +process.env.GOOGLE_CLIENT_SECRET = 'test-google-client-secret'; +``` + +## Unit Tests + +### Testing Google Drive Connector Creation + +```typescript +// __tests__/google-drive-white-label.test.ts +import { createWhiteLabelGDriveConnector } from '@vectorize-io/vectorize-connect'; + +describe('Google Drive White-Label Connector', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should create a Google Drive connector successfully', async () => { + const connectorId = await createWhiteLabelGDriveConnector( + mockConfig, + 'Test Google Drive Connector', + 'test-client-id', + 'test-client-secret' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should handle missing OAuth credentials', async () => { + await expect( + createWhiteLabelGDriveConnector( + mockConfig, + 'Test Connector', + '', + '' + ) + ).rejects.toThrow(); + }); + + it('should handle invalid API credentials', async () => { + const invalidConfig = { + authorization: '', + organizationId: '', + }; + + await expect( + createWhiteLabelGDriveConnector( + invalidConfig, + 'Test Connector', + 'test-client-id', + 'test-client-secret' + ) + ).rejects.toThrow(); + }); +}); +``` + +### Testing OAuth Flow + +```typescript +// __tests__/oauth/google-drive-white-label.test.ts +import { GoogleDriveOAuth } from '@vectorize-io/vectorize-connect'; + +// Mock window.open for popup testing +global.open = jest.fn(); + +describe('Google Drive White-Label OAuth Flow', () => { + const mockConfig = { + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + redirectUri: 'http://localhost:3000/api/oauth/callback', + scopes: ['https://www.googleapis.com/auth/drive.file'], + onSuccess: jest.fn(), + onError: jest.fn(), + }; + + it('should start OAuth flow', () => { + GoogleDriveOAuth.startOAuth(mockConfig); + + expect(global.open).toHaveBeenCalled(); + }); + + it('should handle OAuth success', () => { + const mockResponse = { + selectedFiles: ['file1', 'file2'], + accessToken: 'test-access-token', + }; + + mockConfig.onSuccess(mockResponse); + + expect(mockConfig.onSuccess).toHaveBeenCalledWith(mockResponse); + }); + + it('should handle OAuth error', () => { + const mockError = new Error('OAuth failed'); + + mockConfig.onError(mockError); + + expect(mockConfig.onError).toHaveBeenCalledWith(mockError); + }); + + it('should validate required OAuth parameters', () => { + const invalidConfig = { + ...mockConfig, + clientId: '', + }; + + expect(() => GoogleDriveOAuth.startOAuth(invalidConfig)).toThrow(); + }); +}); +``` + +## Integration Tests + +### Testing Complete Google Drive Flow + +```typescript +// __tests__/integration/google-drive-white-label.test.ts +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import GoogleDriveWhiteLabel from '../components/GoogleDriveWhiteLabel'; + +jest.mock('@vectorize-io/vectorize-connect', () => ({ + createWhiteLabelGDriveConnector: jest.fn().mockResolvedValue('test-connector-id'), + GoogleDriveOAuth: { + startOAuth: jest.fn(), + }, + manageUser: jest.fn().mockResolvedValue({ ok: true }), +})); + +describe('GoogleDriveWhiteLabel Integration', () => { + it('should complete the full connector flow', async () => { + render(); + + // Create connector + const createButton = screen.getByText('Create Google Drive Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText('Connector Created')).toBeInTheDocument(); + }); + + // Start OAuth flow + const connectButton = screen.getByText('Connect with Google Drive'); + fireEvent.click(connectButton); + + expect(require('@vectorize-io/vectorize-connect').GoogleDriveOAuth.startOAuth).toHaveBeenCalled(); + }); + + it('should handle connector creation errors', async () => { + const mockError = new Error('Failed to create connector'); + require('@vectorize-io/vectorize-connect').createWhiteLabelGDriveConnector.mockRejectedValueOnce(mockError); + + render(); + + const createButton = screen.getByText('Create Google Drive Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText(/Failed to create connector/)).toBeInTheDocument(); + }); + }); +}); +``` + +## API Route Tests + +### Testing Google Drive Connector API + +```typescript +// __tests__/api/google-drive-white-label.test.ts +import { POST } from '../../app/api/createGDriveWhiteLabelConnector/route'; +import { NextRequest } from 'next/server'; + +describe('/api/createGDriveWhiteLabelConnector', () => { + it('should create a Google Drive connector successfully', async () => { + const request = new NextRequest('http://localhost:3000/api/createGDriveWhiteLabelConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Google Drive Connector', + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.connectorId).toBeDefined(); + }); + + it('should handle missing OAuth credentials', async () => { + const request = new NextRequest('http://localhost:3000/api/createGDriveWhiteLabelConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toContain('Missing OAuth credentials'); + }); + + it('should handle missing environment variables', async () => { + // Temporarily remove environment variables + const originalApiKey = process.env.VECTORIZE_API_KEY; + delete process.env.VECTORIZE_API_KEY; + + const request = new NextRequest('http://localhost:3000/api/createGDriveWhiteLabelConnector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Connector', + clientId: 'test-client-id', + clientSecret: 'test-client-secret', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(500); + expect(data.error).toContain('Missing Vectorize credentials'); + + // Restore environment variable + process.env.VECTORIZE_API_KEY = originalApiKey; + }); +}); +``` + +### Testing OAuth Callback API + +```typescript +// __tests__/api/oauth-callback-gdrive.test.ts +import { GET } from '../../app/api/oauth/gdrive-callback/route'; +import { NextRequest } from 'next/server'; + +describe('/api/oauth/gdrive-callback', () => { + it('should handle successful OAuth callback', async () => { + const url = new URL('http://localhost:3000/api/oauth/gdrive-callback'); + url.searchParams.set('code', 'test-auth-code'); + url.searchParams.set('state', 'test-state'); + + const request = new NextRequest(url); + const response = await GET(request); + + expect(response.status).toBe(200); + }); + + it('should handle OAuth error callback', async () => { + const url = new URL('http://localhost:3000/api/oauth/gdrive-callback'); + url.searchParams.set('error', 'access_denied'); + + const request = new NextRequest(url); + const response = await GET(request); + + expect(response.status).toBe(500); + }); + + it('should handle missing authorization code', async () => { + const url = new URL('http://localhost:3000/api/oauth/gdrive-callback'); + + const request = new NextRequest(url); + const response = await GET(request); + + expect(response.status).toBe(400); + }); +}); +``` + +## User Management Tests + +### Testing Google Drive User Management + +```typescript +// __tests__/user-management/google-drive-white-label.test.ts +import { manageUser } from '@vectorize-io/vectorize-connect'; + +describe('Google Drive White-Label User Management', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should add user successfully', async () => { + const result = await manageUser( + mockConfig, + 'connector123', + 'user123', + 'add', + { + selectedFiles: ['file1', 'file2'], + accessToken: 'test-access-token' + } + ); + + expect(result).toBeDefined(); + }); + + it('should edit user successfully', async () => { + const result = await manageUser( + mockConfig, + 'connector123', + 'user123', + 'edit', + { + selectedFiles: ['file1', 'file3'], + accessToken: 'test-access-token' + } + ); + + expect(result).toBeDefined(); + }); + + it('should remove user successfully', async () => { + const result = await manageUser( + mockConfig, + 'connector123', + 'user123', + 'remove' + ); + + expect(result).toBeDefined(); + }); + + it('should handle missing access token for add operation', async () => { + await expect( + manageUser( + mockConfig, + 'connector123', + 'user123', + 'add', + { + selectedFiles: ['file1', 'file2'] + } + ) + ).rejects.toThrow(); + }); +}); +``` + +## Manual Testing Checklist + +### Google Drive White-Label Integration +- [ ] Create Google Drive connector with valid OAuth credentials +- [ ] Start OAuth flow and complete authentication +- [ ] Select files using Google Picker +- [ ] Verify files are processed by Vectorize +- [ ] Test file selection editing +- [ ] Test user removal +- [ ] Verify OAuth credentials validation + +### Error Scenarios +- [ ] Invalid OAuth credentials (client ID/secret) +- [ ] Missing environment variables +- [ ] Network connectivity issues +- [ ] Popup blocked by browser +- [ ] OAuth flow cancellation +- [ ] File access permission issues +- [ ] Invalid redirect URI configuration + +### Security Testing +- [ ] OAuth state parameter validation +- [ ] CSRF protection +- [ ] Token expiration handling +- [ ] Secure credential storage + +## Performance Testing + +```typescript +// __tests__/performance/google-drive-white-label.test.ts +describe('Google Drive White-Label Performance Tests', () => { + it('should create connectors within acceptable time', async () => { + const start = Date.now(); + + await createWhiteLabelGDriveConnector( + mockConfig, + 'Performance Test', + 'client-id', + 'client-secret' + ); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(5000); + }); + + it('should handle multiple OAuth flows concurrently', async () => { + const promises = Array.from({ length: 5 }, (_, i) => + createWhiteLabelGDriveConnector( + mockConfig, + `Connector ${i}`, + 'client-id', + 'client-secret' + ) + ); + + const results = await Promise.all(promises); + + expect(results).toHaveLength(5); + results.forEach(result => { + expect(result).toBeDefined(); + }); + }); + + it('should handle large file selections efficiently', async () => { + const largeFileSelection = Array.from({ length: 100 }, (_, i) => ({ + id: `file_${i}`, + name: `File ${i}.pdf`, + size: 1024 * 1024 // 1MB + })); + + const start = Date.now(); + + await manageUser( + mockConfig, + 'connector123', + 'user123', + 'add', + { + selectedFiles: largeFileSelection, + accessToken: 'test-access-token' + } + ); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(10000); // 10 seconds max + }); +}); +``` + +## Running Tests + +```bash +# Run all tests +npm test + +# Run Google Drive specific tests +npm test -- google-drive + +# Run OAuth flow tests +npm test -- oauth + +# Run integration tests +npm test -- integration + +# Run with coverage +npm test -- --coverage + +# Run performance tests +npm test -- performance +``` + +## Next Steps + +- [Google Drive User Management](../../user-management/white-label/google-drive.md) +- [Environment Setup](../../environment-setup/white-label/) diff --git a/docs/testing/white-label/notion.md b/docs/testing/white-label/notion.md new file mode 100644 index 0000000..0b47ba0 --- /dev/null +++ b/docs/testing/white-label/notion.md @@ -0,0 +1,554 @@ +# Testing White-Label Notion Connectors + +This guide covers testing strategies specific to White-Label Notion connectors. + +## Test Environment Setup + +```typescript +// jest.config.js +module.exports = { + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/setupTests.ts'], + moduleNameMapping: { + '^@/(.*)$': '/src/$1', + }, +}; +``` + +```typescript +// src/setupTests.ts +import '@testing-library/jest-dom'; + +// Mock environment variables +process.env.VECTORIZE_API_KEY = 'test-api-key'; +process.env.VECTORIZE_ORGANIZATION_ID = 'test-org-id'; +process.env.NOTION_CLIENT_ID = 'test-notion-client-id'; +process.env.NOTION_CLIENT_SECRET = 'test-notion-client-secret'; +``` + +## Unit Tests + +### Testing Connector Creation + +```typescript +// __tests__/notion-connector.test.ts +import { createWhiteLabelNotionConnector } from '@vectorize-io/vectorize-connect'; + +describe('White-Label Notion Connector Creation', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + it('should create a white-label Notion connector successfully', async () => { + const connectorId = await createWhiteLabelNotionConnector( + mockConfig, + 'Test White-Label Notion Connector', + 'test-client-id', + 'test-client-secret' + ); + + expect(connectorId).toBeDefined(); + expect(typeof connectorId).toBe('string'); + }); + + it('should require client ID and secret', async () => { + await expect( + createWhiteLabelNotionConnector( + mockConfig, + 'Test Connector', + '', // Empty client ID + 'test-client-secret' + ) + ).rejects.toThrow('Client ID and Client Secret are required'); + + await expect( + createWhiteLabelNotionConnector( + mockConfig, + 'Test Connector', + 'test-client-id', + '' // Empty client secret + ) + ).rejects.toThrow('Client ID and Client Secret are required'); + }); + + it('should handle API errors gracefully', async () => { + const invalidConfig = { + authorization: 'invalid-token', + organizationId: 'invalid-org', + }; + + await expect( + createWhiteLabelNotionConnector( + invalidConfig, + 'Test Connector', + 'test-client-id', + 'test-client-secret' + ) + ).rejects.toThrow(); + }); +}); +``` + +### Testing User Management + +```typescript +// __tests__/notion-user-management.test.ts +import { manageNotionUser } from '@vectorize-io/vectorize-connect'; + +describe('Notion User Management', () => { + const mockConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', + }; + + const mockSelectedPages = { + 'page1': { + title: 'Test Page', + pageId: 'page1', + parentType: 'workspace' + } + }; + + it('should add a user successfully', async () => { + const response = await manageNotionUser( + mockConfig, + 'connector123', + mockSelectedPages, + 'notion-access-token', + 'user123', + 'add' + ); + + expect(response).toBeDefined(); + }); + + it('should edit a user successfully', async () => { + const updatedPages = { + 'page2': { + title: 'Updated Page', + pageId: 'page2', + parentType: 'database' + } + }; + + const response = await manageNotionUser( + mockConfig, + 'connector123', + updatedPages, + 'notion-access-token', + 'user123', + 'edit' + ); + + expect(response).toBeDefined(); + }); + + it('should remove a user successfully', async () => { + const response = await manageNotionUser( + mockConfig, + 'connector123', + null, // No pages needed for removal + '', // No access token needed for removal + 'user123', + 'remove' + ); + + expect(response).toBeDefined(); + }); + + it('should require selected pages for add action', async () => { + await expect( + manageNotionUser( + mockConfig, + 'connector123', + null, // Missing pages + 'notion-access-token', + 'user123', + 'add' + ) + ).rejects.toThrow('Selected pages are required for add action'); + }); + + it('should require access token for add action', async () => { + await expect( + manageNotionUser( + mockConfig, + 'connector123', + mockSelectedPages, + '', // Missing access token + 'user123', + 'add' + ) + ).rejects.toThrow('Access token is required for add action'); + }); + + it('should require selected pages for edit action', async () => { + await expect( + manageNotionUser( + mockConfig, + 'connector123', + {}, // Empty pages object + 'notion-access-token', + 'user123', + 'edit' + ) + ).rejects.toThrow('Selected pages are required for edit action'); + }); +}); +``` + +### Testing Token Utilities + +```typescript +// __tests__/notion-tokens.test.ts +import { + exchangeNotionCodeForTokens, + refreshNotionToken +} from '@vectorize-io/vectorize-connect'; + +describe('Notion Token Utilities', () => { + it('should exchange code for tokens', async () => { + const tokens = await exchangeNotionCodeForTokens( + 'auth-code', + 'client-id', + 'client-secret', + 'https://example.com/callback' + ); + + expect(tokens).toBeDefined(); + }); + + it('should validate access token', async () => { + const validatedToken = await refreshNotionToken('access-token'); + + expect(validatedToken).toBeDefined(); + }); + + it('should handle invalid authorization code', async () => { + await expect( + exchangeNotionCodeForTokens( + 'invalid-code', + 'client-id', + 'client-secret', + 'https://example.com/callback' + ) + ).rejects.toThrow(); + }); + + it('should handle invalid access token', async () => { + await expect( + refreshNotionToken('invalid-token') + ).rejects.toThrow(); + }); +}); +``` + +## Integration Tests + +### Testing Complete OAuth Flow + +```typescript +// __tests__/notion-oauth-flow.test.ts +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import NotionConnector from '../components/NotionConnector'; + +// Mock the SDK functions +jest.mock('@vectorize-io/vectorize-connect', () => ({ + createWhiteLabelNotionConnector: jest.fn().mockResolvedValue('test-notion-connector-id'), + getOneTimeConnectorToken: jest.fn().mockResolvedValue({ token: 'test-token' }), + manageNotionUser: jest.fn().mockResolvedValue({ ok: true }), + NotionOAuth: jest.fn().mockImplementation(() => ({ + redirectToVectorizeConnect: jest.fn(), + })), +})); + +describe('Notion OAuth Flow Integration', () => { + it('should complete the full connector flow', async () => { + render(); + + // Create connector + const createButton = screen.getByText('Create Notion Connector'); + fireEvent.click(createButton); + + await waitFor(() => { + expect(screen.getByText('Connector Created')).toBeInTheDocument(); + }); + + // Connect user + const connectButton = screen.getByText('Connect to Notion'); + fireEvent.click(connectButton); + + await waitFor(() => { + expect(screen.getByText('Connecting...')).toBeInTheDocument(); + }); + }); + + it('should handle OAuth errors gracefully', async () => { + // Mock OAuth error + const mockNotionOAuth = require('@vectorize-io/vectorize-connect').NotionOAuth; + mockNotionOAuth.mockImplementation(() => ({ + redirectToVectorizeConnect: jest.fn().mockRejectedValue(new Error('OAuth failed')), + })); + + render(); + + const connectButton = screen.getByText('Connect to Notion'); + fireEvent.click(connectButton); + + await waitFor(() => { + expect(screen.getByText('Connection failed')).toBeInTheDocument(); + }); + }); +}); +``` + +## API Route Tests + +### Testing Connector Creation API + +```typescript +// __tests__/api/create-notion-connector.test.ts +import { POST } from '../../app/api/create-notion-connector/route'; +import { NextRequest } from 'next/server'; + +describe('/api/create-notion-connector', () => { + it('should create a connector successfully', async () => { + const request = new NextRequest('http://localhost:3000/api/create-notion-connector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Notion Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.connectorId).toBeDefined(); + }); + + it('should handle missing connector name', async () => { + const request = new NextRequest('http://localhost:3000/api/create-notion-connector', { + method: 'POST', + body: JSON.stringify({}), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBeDefined(); + }); + + it('should handle missing OAuth credentials', async () => { + // Temporarily remove environment variables + const originalClientId = process.env.NOTION_CLIENT_ID; + const originalClientSecret = process.env.NOTION_CLIENT_SECRET; + + delete process.env.NOTION_CLIENT_ID; + delete process.env.NOTION_CLIENT_SECRET; + + const request = new NextRequest('http://localhost:3000/api/create-notion-connector', { + method: 'POST', + body: JSON.stringify({ + connectorName: 'Test Connector', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(500); + expect(data.error).toBeDefined(); + + // Restore environment variables + process.env.NOTION_CLIENT_ID = originalClientId; + process.env.NOTION_CLIENT_SECRET = originalClientSecret; + }); +}); +``` + +### Testing OAuth Callback API + +```typescript +// __tests__/api/notion-oauth-callback.test.ts +import { POST } from '../../app/api/complete-notion-oauth/route'; +import { NextRequest } from 'next/server'; + +describe('/api/complete-notion-oauth', () => { + it('should complete OAuth flow successfully', async () => { + const request = new NextRequest('http://localhost:3000/api/complete-notion-oauth', { + method: 'POST', + body: JSON.stringify({ + code: 'valid-auth-code', + userId: 'user123', + connectorId: 'connector123', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.success).toBe(true); + }); + + it('should handle invalid authorization code', async () => { + const request = new NextRequest('http://localhost:3000/api/complete-notion-oauth', { + method: 'POST', + body: JSON.stringify({ + code: 'invalid-code', + userId: 'user123', + connectorId: 'connector123', + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBeDefined(); + }); +}); +``` + +## Manual Testing Checklist + +### Connector Creation +- [ ] Create white-label Notion connector with valid OAuth credentials +- [ ] Handle missing client ID gracefully +- [ ] Handle missing client secret gracefully +- [ ] Verify connector appears in Vectorize dashboard + +### OAuth Flow +- [ ] Redirect to Notion OAuth page with correct parameters +- [ ] Handle successful OAuth callback +- [ ] Handle OAuth errors (user denial, invalid credentials) +- [ ] Exchange authorization code for access tokens + +### User Management +- [ ] Add user with selected Notion pages +- [ ] Edit user's page selection +- [ ] Remove user from connector +- [ ] Handle user management errors + +### Page Selection +- [ ] Display available Notion pages +- [ ] Allow multi-page selection +- [ ] Handle pages from different workspaces +- [ ] Validate page access permissions + +### Error Scenarios +- [ ] Network connectivity issues +- [ ] Invalid OAuth credentials +- [ ] Expired access tokens +- [ ] Missing environment variables +- [ ] Notion API rate limiting + +## Performance Testing + +```typescript +// __tests__/notion-performance.test.ts +describe('Notion Performance Tests', () => { + it('should create connectors within acceptable time', async () => { + const start = Date.now(); + + await createWhiteLabelNotionConnector( + mockConfig, + 'Performance Test', + 'client-id', + 'client-secret' + ); + + const duration = Date.now() - start; + expect(duration).toBeLessThan(5000); // 5 seconds max + }); + + it('should handle concurrent user management requests', async () => { + const promises = Array.from({ length: 5 }, (_, i) => + manageNotionUser( + mockConfig, + 'connector123', + mockSelectedPages, + 'access-token', + `user${i}`, + 'add' + ) + ); + + const results = await Promise.all(promises); + + expect(results).toHaveLength(5); + results.forEach(result => { + expect(result).toBeDefined(); + }); + }); +}); +``` + +## Test Data Setup + +```typescript +// __tests__/helpers/notionTestData.ts +export const mockNotionConfig = { + authorization: 'test-api-key', + organizationId: 'test-org-id', +}; + +export const mockNotionCredentials = { + clientId: 'test-notion-client-id', + clientSecret: 'test-notion-client-secret', +}; + +export const mockNotionPages = { + 'page1': { + title: 'Project Documentation', + pageId: 'page1', + parentType: 'workspace' + }, + 'page2': { + title: 'Meeting Notes', + pageId: 'page2', + parentType: 'database' + }, + 'page3': { + title: 'Task List', + pageId: 'page3', + parentType: 'workspace' + } +}; + +export const mockNotionTokens = { + access_token: 'notion_access_token_123', + token_type: 'bearer', + bot_id: 'bot_123', + workspace_name: 'Test Workspace', + workspace_id: 'workspace_123' +}; +``` + +## Running Tests + +```bash +# Run all Notion tests +npm test -- --testPathPattern=notion + +# Run tests in watch mode +npm test -- --watch --testPathPattern=notion + +# Run tests with coverage +npm test -- --coverage --testPathPattern=notion + +# Run specific test file +npm test -- notion-connector.test.ts + +# Run integration tests only +npm test -- --testPathPattern=notion.*integration +``` + +## Next Steps + +- [Environment Setup](../../environment-setup/white-label/) +- [Creating Connectors](../../creating-connectors/white-label/notion.md) +- [User Management](../../user-management/white-label/) diff --git a/docs/types.md b/docs/types.md index 46113f7..959e29a 100644 --- a/docs/types.md +++ b/docs/types.md @@ -325,10 +325,10 @@ Configuration for Vectorize API requests. ```typescript type VectorizeAPIConfig = { - /** Bearer token (authorization) - use VECTORIZE_TOKEN environment variable */ + /** Bearer token (authorization) - use VECTORIZE_API_KEY environment variable */ authorization: string; - /** Organization ID - use VECTORIZE_ORG environment variable */ + /** Organization ID - use VECTORIZE_ORGANIZATION_ID environment variable */ organizationId: string; }; ``` @@ -342,8 +342,8 @@ type VectorizeAPIConfig = { ```typescript const config: VectorizeAPIConfig = { - authorization: process.env.VECTORIZE_TOKEN!, - organizationId: process.env.VECTORIZE_ORG! + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! }; // Use the config with API functions diff --git a/docs/user-management/README.md b/docs/user-management/README.md new file mode 100644 index 0000000..dc703a9 --- /dev/null +++ b/docs/user-management/README.md @@ -0,0 +1,76 @@ +# User Management + +This section covers how to manage users in connectors using different approaches. + +## Approaches + +- **[Vectorize](./vectorize/)** - User management with Vectorize's managed flow +- **[White-Label](./white-label/)** - User management with custom OAuth flows + +## Overview + +User management involves adding, editing, and removing users from connectors. The approach differs based on whether you're using Vectorize's managed OAuth or your own OAuth applications. + +### Vectorize Approach +- Users are automatically added through Vectorize's authentication flow +- Use `manageUser` function for editing and removing users +- Simplified user management with consistent API + +### White-Label Approach +- Manual user management through API calls +- Platform-specific user management functions available +- Full control over user lifecycle + +## Quick Reference + +### Generic User Management +```typescript +import { manageUser } from '@vectorize-io/vectorize-connect'; + +// Add/edit user +await manageUser(config, connectorId, userId, 'add', payload); + +// Remove user +await manageUser(config, connectorId, userId, 'remove'); +``` + +### Platform-Specific User Management +```typescript +import { managePlatformUser } from '@vectorize-io/vectorize-connect'; + +await managePlatformUser( + config, + connectorId, + selectedFiles, + refreshToken, + userId, + action +); +``` + +## Common Operations + +### Adding Users +Users are typically added during the authentication flow, but can also be added programmatically. + +### Editing User File Selection +Allow users to modify their file selection without re-authenticating. + +### Removing Users +Remove users from connectors when they no longer need access. + +## Error Handling + +```typescript +try { + await manageUser(config, connectorId, userId, action, payload); +} catch (error) { + if (error.response?.status === 404) { + console.error('Connector or user not found'); + } else if (error.response?.status === 403) { + console.error('Insufficient permissions'); + } else { + console.error('User management failed:', error.message); + } +} +``` diff --git a/docs/user-management/vectorize/README.md b/docs/user-management/vectorize/README.md new file mode 100644 index 0000000..e912638 --- /dev/null +++ b/docs/user-management/vectorize/README.md @@ -0,0 +1,173 @@ +# User Management - Vectorize Approach + +Manage users in Vectorize connectors using the simplified API. + +## Automatic User Addition + +With Vectorize connectors, users are automatically added when they complete the authentication flow through `PlatformOAuth.redirectToVectorizeConnect()`. No additional API calls are required for adding users. + +## Manual User Management + +For editing or removing users, use the `manageUser` function: + +```typescript +import { manageUser } from '@vectorize-io/vectorize-connect'; + +// Edit user file selection +await manageUser( + vectorizeConfig, + connectorId, + userId, + 'edit', + { selectedFiles: newFileSelection } +); + +// Remove a user +await manageUser( + vectorizeConfig, + connectorId, + userId, + 'remove' +); +``` + +## User Management API Route + +Create an optional API route for user management operations: + +```typescript +// app/api/manage-user/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { manageUser } from '@vectorize-io/vectorize-connect'; + +interface VectorizeAPIConfig { + organizationId: string; + authorization: string; +} + +export async function POST(request: NextRequest) { + try { + const { connectorId, userId, action, payload } = await request.json(); + + // Validate required parameters + if (!connectorId || !userId || !action) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 } + ); + } + + // Get Vectorize config + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", + }; + + // Manage the user + const response = await manageUser( + config, + connectorId, + userId, + action, + payload + ); + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error: any) { + console.error('Error managing user:', error); + return NextResponse.json( + { error: error.message || "Failed to manage user" }, + { status: 500 } + ); + } +} +``` + +## Frontend User Management + +```typescript +const removeUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove user'); + } + + console.log('User removed successfully'); + } catch (error: any) { + console.error('Error removing user:', error.message); + } +}; + +const editUserFiles = async (userId: string, newFileSelection: any) => { + try { + const response = await fetch("/api/manage-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "edit", + payload: { selectedFiles: newFileSelection } + }), + }); + + if (!response.ok) { + throw new Error('Failed to update user files'); + } + + console.log('User files updated successfully'); + } catch (error: any) { + console.error('Error updating user files:', error.message); + } +}; +``` + +## Editing User File Selection + +To allow users to modify their file selection, generate a new one-time token and redirect them to the edit flow: + +```typescript +import { getOneTimeConnectorToken, PlatformOAuth } from '@vectorize-io/vectorize-connect'; + +const editUserFiles = async (userId: string) => { + try { + // Generate token for editing + const tokenResponse = await getOneTimeConnectorToken( + vectorizeConfig, + userId, + connectorId + ); + + // Redirect to Vectorize edit flow + await PlatformOAuth.redirectToVectorizeEdit( + tokenResponse.token, + vectorizeConfig.organizationId + ); + } catch (error) { + console.error('Failed to start edit flow:', error); + } +}; +``` + +## Platform-Specific Examples + +For detailed platform-specific user management examples: + +- [Google Drive User Management](./google-drive.md) +- [Dropbox User Management](./dropbox.md) +- [Notion User Management](./notion.md) + +## Next Steps + +- [Frontend Implementation](../../frontend-implementation/vectorize/) +- [Testing](../../testing/vectorize/) diff --git a/docs/user-management/vectorize/dropbox.md b/docs/user-management/vectorize/dropbox.md new file mode 100644 index 0000000..e895c88 --- /dev/null +++ b/docs/user-management/vectorize/dropbox.md @@ -0,0 +1,282 @@ +# Dropbox User Management - Vectorize Approach + +Manage Dropbox users in Vectorize connectors. + +## Automatic User Addition + +With Vectorize Dropbox connectors, users are automatically added when they complete the authentication flow through `DropboxOAuth.redirectToVectorizeConnect()`. No additional API calls are required for adding users. + +## Manual User Management + +For editing or removing users, use the `manageUser` function: + +```typescript +import { manageUser, VectorizeAPIConfig } from '@vectorize-io/vectorize-connect'; + +const config: VectorizeAPIConfig = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! +}; + +// Edit user file selection +await manageUser( + config, + connectorId, + userId, + 'edit', + { selectedFiles: newDropboxFileSelection } +); + +// Remove a user +await manageUser( + config, + connectorId, + userId, + 'remove' +); +``` + +## Dropbox User Management API Route + +Create an API route specifically for Dropbox user management: + +```typescript +// app/api/manage-dropbox-user/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { manageUser, VectorizeAPIConfig } from '@vectorize-io/vectorize-connect'; + +export async function POST(request: NextRequest) { + try { + const { connectorId, userId, action, selectedFiles } = await request.json(); + + if (!connectorId || !userId || !action) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 } + ); + } + + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, + authorization: process.env.VECTORIZE_API_KEY!, + }; + + // Handle Dropbox specific user management + const payload = action === 'edit' ? { selectedFiles } : undefined; + + const response = await manageUser( + config, + connectorId, + userId, + action, + payload + ); + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error: any) { + console.error('Error managing Dropbox user:', error); + return NextResponse.json( + { error: error.message || "Failed to manage Dropbox user" }, + { status: 500 } + ); + } +} +``` + +## Frontend Dropbox User Management + +```typescript +import { DropboxOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +const removeDropboxUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-dropbox-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Dropbox user'); + } + + console.log('Dropbox user removed successfully'); + } catch (error: any) { + console.error('Error removing Dropbox user:', error.message); + } +}; + +const editDropboxUserFiles = async (userId: string) => { + try { + const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! + }; + + // Generate token for editing + const tokenResponse = await getOneTimeConnectorToken( + config, + userId, + connectorId + ); + + // Redirect to Vectorize Dropbox edit flow + await DropboxOAuth.redirectToVectorizeEdit( + tokenResponse.token, + config.organizationId + ); + } catch (error) { + console.error('Failed to start Dropbox edit flow:', error); + } +}; +``` + +## Complete Dropbox User Management Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { DropboxOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +interface DropboxUser { + id: string; + email: string; + name: string; + selectedFiles: string[]; +} + +export default function DropboxUserManager({ connectorId }: { connectorId: string }) { + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const removeUser = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + const response = await fetch("/api/manage-dropbox-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Dropbox user'); + } + + // Update local state + setUsers(users.filter(user => user.id !== userId)); + + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + const editUserFiles = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + // Get one-time token for editing + const tokenResponse = await fetch( + `/api/get-dropbox-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize edit flow + await DropboxOAuth.redirectToVectorizeEdit( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + return ( +
+

Dropbox Users

+ + {error && ( +
+ {error} +
+ )} + +
+ {users.map((user) => ( +
+
+
{user.name}
+
{user.email}
+
+ {user.selectedFiles.length} files selected +
+
+ +
+ + + +
+
+ ))} +
+
+ ); +} +``` + +## Error Handling + +```typescript +try { + await manageUser(config, connectorId, userId, 'remove'); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API credentials'); + } else if (error.response?.status === 404) { + console.error('Dropbox connector or user not found'); + } else if (error.response?.status === 403) { + console.error('Insufficient permissions to manage Dropbox user'); + } else { + console.error('Dropbox user management failed:', error.message); + } +} +``` + +## Next Steps + +- [Dropbox Frontend Implementation](../../frontend-implementation/vectorize/dropbox.md) +- [Dropbox Testing](../../testing/vectorize/dropbox.md) diff --git a/docs/user-management/vectorize/google-drive.md b/docs/user-management/vectorize/google-drive.md new file mode 100644 index 0000000..2295bd0 --- /dev/null +++ b/docs/user-management/vectorize/google-drive.md @@ -0,0 +1,282 @@ +# Google Drive User Management - Vectorize Approach + +Manage Google Drive users in Vectorize connectors. + +## Automatic User Addition + +With Vectorize Google Drive connectors, users are automatically added when they complete the authentication flow through `GoogleDriveOAuth.redirectToVectorizeConnect()`. No additional API calls are required for adding users. + +## Manual User Management + +For editing or removing users, use the `manageUser` function: + +```typescript +import { manageUser, VectorizeAPIConfig } from '@vectorize-io/vectorize-connect'; + +const config: VectorizeAPIConfig = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! +}; + +// Edit user file selection +await manageUser( + config, + connectorId, + userId, + 'edit', + { selectedFiles: newGoogleDriveFileSelection } +); + +// Remove a user +await manageUser( + config, + connectorId, + userId, + 'remove' +); +``` + +## Google Drive User Management API Route + +Create an API route specifically for Google Drive user management: + +```typescript +// app/api/manage-gdrive-user/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { manageUser, VectorizeAPIConfig } from '@vectorize-io/vectorize-connect'; + +export async function POST(request: NextRequest) { + try { + const { connectorId, userId, action, selectedFiles } = await request.json(); + + if (!connectorId || !userId || !action) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 } + ); + } + + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, + authorization: process.env.VECTORIZE_API_KEY!, + }; + + // Handle Google Drive specific user management + const payload = action === 'edit' ? { selectedFiles } : undefined; + + const response = await manageUser( + config, + connectorId, + userId, + action, + payload + ); + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error: any) { + console.error('Error managing Google Drive user:', error); + return NextResponse.json( + { error: error.message || "Failed to manage Google Drive user" }, + { status: 500 } + ); + } +} +``` + +## Frontend Google Drive User Management + +```typescript +import { GoogleDriveOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +const removeGoogleDriveUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-gdrive-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Google Drive user'); + } + + console.log('Google Drive user removed successfully'); + } catch (error: any) { + console.error('Error removing Google Drive user:', error.message); + } +}; + +const editGoogleDriveUserFiles = async (userId: string) => { + try { + const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! + }; + + // Generate token for editing + const tokenResponse = await getOneTimeConnectorToken( + config, + userId, + connectorId + ); + + // Redirect to Vectorize Google Drive edit flow + await GoogleDriveOAuth.redirectToVectorizeEdit( + tokenResponse.token, + config.organizationId + ); + } catch (error) { + console.error('Failed to start Google Drive edit flow:', error); + } +}; +``` + +## Complete Google Drive User Management Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { GoogleDriveOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +interface GoogleDriveUser { + id: string; + email: string; + name: string; + selectedFiles: string[]; +} + +export default function GoogleDriveUserManager({ connectorId }: { connectorId: string }) { + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const removeUser = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + const response = await fetch("/api/manage-gdrive-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Google Drive user'); + } + + // Update local state + setUsers(users.filter(user => user.id !== userId)); + + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + const editUserFiles = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + // Get one-time token for editing + const tokenResponse = await fetch( + `/api/get-gdrive-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize edit flow + await GoogleDriveOAuth.redirectToVectorizeEdit( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + return ( +
+

Google Drive Users

+ + {error && ( +
+ {error} +
+ )} + +
+ {users.map((user) => ( +
+
+
{user.name}
+
{user.email}
+
+ {user.selectedFiles.length} files selected +
+
+ +
+ + + +
+
+ ))} +
+
+ ); +} +``` + +## Error Handling + +```typescript +try { + await manageUser(config, connectorId, userId, 'remove'); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API credentials'); + } else if (error.response?.status === 404) { + console.error('Google Drive connector or user not found'); + } else if (error.response?.status === 403) { + console.error('Insufficient permissions to manage Google Drive user'); + } else { + console.error('Google Drive user management failed:', error.message); + } +} +``` + +## Next Steps + +- [Google Drive Frontend Implementation](../../frontend-implementation/vectorize/google-drive.md) +- [Google Drive Testing](../../testing/vectorize/google-drive.md) diff --git a/docs/user-management/vectorize/notion.md b/docs/user-management/vectorize/notion.md new file mode 100644 index 0000000..edbb4b6 --- /dev/null +++ b/docs/user-management/vectorize/notion.md @@ -0,0 +1,293 @@ +# Notion User Management - Vectorize Approach + +Manage Notion users in Vectorize connectors. + +## Automatic User Addition + +With Vectorize Notion connectors, users are automatically added when they complete the authentication flow through `NotionOAuth.redirectToVectorizeConnect()`. No additional API calls are required for adding users. + +## Manual User Management + +For editing or removing users, use the `manageNotionUser` function: + +```typescript +import { manageNotionUser, VectorizeAPIConfig } from '@vectorize-io/vectorize-connect'; + +const config: VectorizeAPIConfig = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! +}; + +// Edit user page selection +const selectedPages = { + "page1": { + title: "My Page", + pageId: "page1", + parentType: "workspace" + } +}; + +await manageNotionUser( + config, + connectorId, + selectedPages, + accessToken, + userId, + 'edit' +); + +// Remove a user +await manageNotionUser( + config, + connectorId, + null, + '', + userId, + 'remove' +); +``` + +## Notion User Management API Route + +Create an API route specifically for Notion user management: + +```typescript +// app/api/manage-notion-user/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { manageNotionUser, VectorizeAPIConfig } from '@vectorize-io/vectorize-connect'; + +export async function POST(request: NextRequest) { + try { + const { connectorId, userId, action, selectedPages, accessToken } = await request.json(); + + if (!connectorId || !userId || !action) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 } + ); + } + + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, + authorization: process.env.VECTORIZE_API_KEY!, + }; + + // Handle Notion specific user management + const response = await manageNotionUser( + config, + connectorId, + action === 'remove' ? null : selectedPages, + action === 'remove' ? '' : accessToken, + userId, + action + ); + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error: any) { + console.error('Error managing Notion user:', error); + return NextResponse.json( + { error: error.message || "Failed to manage Notion user" }, + { status: 500 } + ); + } +} +``` + +## Frontend Notion User Management + +```typescript +import { NotionOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +const removeNotionUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-notion-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Notion user'); + } + + console.log('Notion user removed successfully'); + } catch (error: any) { + console.error('Error removing Notion user:', error.message); + } +}; + +const editNotionUserPages = async (userId: string) => { + try { + const config = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! + }; + + // Generate token for editing + const tokenResponse = await getOneTimeConnectorToken( + config, + userId, + connectorId + ); + + // Redirect to Vectorize Notion edit flow + await NotionOAuth.redirectToVectorizeEdit( + tokenResponse.token, + config.organizationId + ); + } catch (error) { + console.error('Failed to start Notion edit flow:', error); + } +}; +``` + +## Complete Notion User Management Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { NotionOAuth, getOneTimeConnectorToken } from '@vectorize-io/vectorize-connect'; + +interface NotionUser { + id: string; + workspaceName: string; + selectedPages: Record; +} + +export default function NotionUserManager({ connectorId }: { connectorId: string }) { + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const removeUser = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + const response = await fetch("/api/manage-notion-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Notion user'); + } + + // Update local state + setUsers(users.filter(user => user.id !== userId)); + + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + const editUserPages = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + // Get one-time token for editing + const tokenResponse = await fetch( + `/api/get-notion-token?userId=${userId}&connectorId=${connectorId}` + ).then(response => { + if (!response.ok) { + throw new Error(`Failed to generate token. Status: ${response.status}`); + } + return response.json(); + }); + + // Redirect to Vectorize edit flow + await NotionOAuth.redirectToVectorizeEdit( + tokenResponse.token, + process.env.NEXT_PUBLIC_VECTORIZE_ORGANIZATION_ID! + ); + + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + return ( +
+

Notion Users

+ + {error && ( +
+ {error} +
+ )} + +
+ {users.map((user) => ( +
+
+
{user.workspaceName}
+
+ {Object.keys(user.selectedPages).length} pages selected +
+
+ {Object.values(user.selectedPages).map(page => page.title).join(', ')} +
+
+ +
+ + + +
+
+ ))} +
+
+ ); +} +``` + +## Error Handling + +```typescript +try { + await manageNotionUser(config, connectorId, null, '', userId, 'remove'); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API credentials'); + } else if (error.response?.status === 404) { + console.error('Notion connector or user not found'); + } else if (error.response?.status === 403) { + console.error('Insufficient permissions to manage Notion user'); + } else { + console.error('Notion user management failed:', error.message); + } +} +``` + +## Next Steps + +- [Notion Frontend Implementation](../../frontend-implementation/vectorize/notion.md) +- [Notion Testing](../../testing/vectorize/notion.md) diff --git a/docs/user-management/white-label/README.md b/docs/user-management/white-label/README.md new file mode 100644 index 0000000..4d50cf5 --- /dev/null +++ b/docs/user-management/white-label/README.md @@ -0,0 +1,81 @@ +# User Management - White-Label Approach + +Manage users in White-Label connectors using your own OAuth credentials. + +## Complete User Lifecycle Management + +With White-Label connectors, you have full control over the user lifecycle and must explicitly manage all user operations: + +### Add Users +Users must be explicitly added after completing OAuth authentication: + +```typescript +import { manageUser, manageNotionUser } from '@vectorize-io/vectorize-connect'; + +// For Google Drive and Dropbox +await manageUser( + config, + connectorId, + userId, + 'add', + { selectedFiles: userFileSelection, accessToken: oauthToken } +); + +// For Notion (uses specialized function) +await manageNotionUser( + config, + connectorId, + selectedPages, + accessToken, + userId, + 'add' +); +``` + +### Edit Users +Update user file/page selections: + +```typescript +// Update file selection for Google Drive/Dropbox +await manageUser( + config, + connectorId, + userId, + 'edit', + { selectedFiles: newFileSelection, accessToken: refreshedToken } +); + +// Update page selection for Notion +await manageNotionUser( + config, + connectorId, + newSelectedPages, + refreshedAccessToken, + userId, + 'edit' +); +``` + +### Remove Users +Remove users from connectors: + +```typescript +// Remove user (works for all platforms) +await manageUser(config, connectorId, userId, 'remove'); + +// For Notion, you can also use the specialized function +await manageNotionUser(config, connectorId, null, '', userId, 'remove'); +``` + +## Platform-Specific Examples + +For detailed platform-specific user management examples: + +- [Google Drive User Management](./google-drive.md) +- [Dropbox User Management](./dropbox.md) +- [Notion User Management](./notion.md) + +## Next Steps + +- [Frontend Implementation](../../frontend-implementation/white-label/) +- [Testing](../../testing/white-label/) diff --git a/docs/user-management/white-label/dropbox.md b/docs/user-management/white-label/dropbox.md new file mode 100644 index 0000000..ff62235 --- /dev/null +++ b/docs/user-management/white-label/dropbox.md @@ -0,0 +1,211 @@ +# Dropbox User Management - White-Label Approach + +Manage Dropbox users in white-label connectors. + +## User Management API Route + +Create a file at `app/api/manage-dropbox-user/route.ts`: + +```typescript +// app/api/manage-dropbox-user/route.ts +import { NextRequest, NextResponse } from "next/server"; +import { manageDropboxUser } from "@vectorize-io/vectorize-connect"; + +interface VectorizeAPIConfig { + organizationId: string; + authorization: string; +} + +export async function POST(request: NextRequest) { + try { + // Get request body + const body = await request.json(); + const { connectorId, selectedFiles, refreshToken, userId, action } = body; + + // Validate required parameters + if (!connectorId || !refreshToken || !userId || !action) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 } + ); + } + + // Configure Vectorize API + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", + }; + + // Manage the user + const response = await manageDropboxUser( + config, + connectorId, + selectedFiles, + refreshToken, + userId, + action + ); + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error: any) { + console.error('Error managing Dropbox user:', error); + return NextResponse.json( + { error: error.message || "Failed to manage user" }, + { status: 500 } + ); + } +} +``` + +## Frontend User Management + +```typescript +const addUserToConnector = async (connectorId: string, selectedFiles: Record, refreshToken: string) => { + try { + const response = await fetch("/api/manage-dropbox-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + selectedFiles, + refreshToken, + userId: "user123", // Replace with your user ID + action: "add" + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to add user to connector'); + } + + console.log('User added to connector successfully'); + } catch (error: any) { + console.error('Error adding user:', error.message); + } +}; + +const removeUserFromConnector = async (connectorId: string, userId: string) => { + try { + const response = await fetch("/api/manage-dropbox-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + selectedFiles: {}, + refreshToken: "", + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove user from connector'); + } + + console.log('User removed from connector successfully'); + } catch (error: any) { + console.error('Error removing user:', error.message); + } +}; + +const updateUserFiles = async (connectorId: string, userId: string, newSelectedFiles: Record, refreshToken: string) => { + try { + const response = await fetch("/api/manage-dropbox-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + selectedFiles: newSelectedFiles, + refreshToken, + userId, + action: "edit" + }), + }); + + if (!response.ok) { + throw new Error('Failed to update user files'); + } + + console.log('User files updated successfully'); + } catch (error: any) { + console.error('Error updating user files:', error.message); + } +}; +``` + +## Complete User Management Component + +```typescript +'use client'; + +import { useState } from 'react'; + +interface User { + id: string; + name: string; + selectedFiles: Record; + refreshToken: string; +} + +export default function DropboxUserManagement() { + const [users, setUsers] = useState([]); + const [connectorId, setConnectorId] = useState(''); + + const removeUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-dropbox-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + selectedFiles: {}, + refreshToken: "", + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove user'); + } + + // Update local state + setUsers(users.filter(user => user.id !== userId)); + console.log('User removed successfully'); + } catch (error: any) { + console.error('Error removing user:', error.message); + } + }; + + return ( +
+

Dropbox User Management

+ +
+ {users.map(user => ( +
+
+

{user.name}

+

+ {Object.keys(user.selectedFiles).length} files selected +

+
+ +
+ ))} +
+
+ ); +} +``` + +## Next Steps + +- [Frontend Implementation](../../frontend-implementation/white-label/) +- [Testing](../../testing/white-label/) diff --git a/docs/user-management/white-label/google-drive.md b/docs/user-management/white-label/google-drive.md new file mode 100644 index 0000000..2435670 --- /dev/null +++ b/docs/user-management/white-label/google-drive.md @@ -0,0 +1,209 @@ +# Google Drive User Management - White-Label Approach + +Manage Google Drive users in white-label connectors. + +## User Management API Route + +Create a file at `app/api/manage-gdrive-user/route.ts`: + +```typescript +// app/api/manage-gdrive-user/route.ts +import { NextRequest, NextResponse } from "next/server"; +import { manageGDriveUser } from "@vectorize-io/vectorize-connect"; + +interface VectorizeAPIConfig { + organizationId: string; + authorization: string; +} + +export async function POST(request: NextRequest) { + try { + // Get request body + const body = await request.json(); + const { connectorId, fileIds, refreshToken, userId, action } = body; + + // Validate required parameters + if (!connectorId || !refreshToken || !userId || !action) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 } + ); + } + + // Configure Vectorize API + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID ?? "", + authorization: process.env.VECTORIZE_API_KEY ?? "", + }; + + // Manage the user + const response = await manageGDriveUser( + config, + connectorId, + fileIds, + refreshToken, + userId, + action + ); + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error: any) { + console.error('Error managing Google Drive user:', error); + return NextResponse.json( + { error: error.message || "Failed to manage user" }, + { status: 500 } + ); + } +} +``` + +## Frontend User Management + +```typescript +const addUserToConnector = async (connectorId: string, fileIds: string[], refreshToken: string) => { + try { + const response = await fetch("/api/manage-gdrive-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + fileIds, + refreshToken, + userId: "user123", // Replace with your user ID + action: "add" + }), + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Failed to add user to connector'); + } + + console.log('User added to connector successfully'); + } catch (error: any) { + console.error('Error adding user:', error.message); + } +}; + +const removeUserFromConnector = async (connectorId: string, userId: string) => { + try { + const response = await fetch("/api/manage-gdrive-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + fileIds: [], + refreshToken: "", + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove user from connector'); + } + + console.log('User removed from connector successfully'); + } catch (error: any) { + console.error('Error removing user:', error.message); + } +}; + +const updateUserFiles = async (connectorId: string, userId: string, newFileIds: string[], refreshToken: string) => { + try { + const response = await fetch("/api/manage-gdrive-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + fileIds: newFileIds, + refreshToken, + userId, + action: "edit" + }), + }); + + if (!response.ok) { + throw new Error('Failed to update user files'); + } + + console.log('User files updated successfully'); + } catch (error: any) { + console.error('Error updating user files:', error.message); + } +}; +``` + +## Complete User Management Component + +```typescript +'use client'; + +import { useState } from 'react'; + +interface User { + id: string; + name: string; + fileIds: string[]; + refreshToken: string; +} + +export default function GoogleDriveUserManagement() { + const [users, setUsers] = useState([]); + const [connectorId, setConnectorId] = useState(''); + + const removeUser = async (userId: string) => { + try { + const response = await fetch("/api/manage-gdrive-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + fileIds: [], + refreshToken: "", + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove user'); + } + + // Update local state + setUsers(users.filter(user => user.id !== userId)); + console.log('User removed successfully'); + } catch (error: any) { + console.error('Error removing user:', error.message); + } + }; + + return ( +
+

Google Drive User Management

+ +
+ {users.map(user => ( +
+
+

{user.name}

+

{user.fileIds.length} files selected

+
+ +
+ ))} +
+
+ ); +} +``` + +## Next Steps + +- [Frontend Implementation](../../frontend-implementation/white-label/) +- [Testing](../../testing/white-label/) diff --git a/docs/user-management/white-label/notion.md b/docs/user-management/white-label/notion.md new file mode 100644 index 0000000..e06a718 --- /dev/null +++ b/docs/user-management/white-label/notion.md @@ -0,0 +1,417 @@ +# Notion User Management - White-Label Approach + +Manage Notion users in White-Label connectors using your own OAuth credentials. + +## User Addition + +With White-Label Notion connectors, users are added through the OAuth flow and then managed using the `manageNotionUser` function: + +```typescript +import { manageNotionUser, VectorizeAPIConfig } from '@vectorize-io/vectorize-connect'; + +const config: VectorizeAPIConfig = { + authorization: process.env.VECTORIZE_API_KEY!, + organizationId: process.env.VECTORIZE_ORGANIZATION_ID! +}; + +// Add user with selected pages +const selectedPages = { + "page1": { + title: "My Page", + pageId: "page1", + parentType: "workspace" + }, + "page2": { + title: "Another Page", + pageId: "page2", + parentType: "database" + } +}; + +await manageNotionUser( + config, + connectorId, + selectedPages, + accessToken, + userId, + 'add' +); +``` + +## Complete User Management + +### Add User +```typescript +const addNotionUser = async ( + userId: string, + selectedPages: Record, + accessToken: string +) => { + try { + await manageNotionUser( + config, + connectorId, + selectedPages, + accessToken, + userId, + 'add' + ); + console.log('Notion user added successfully'); + } catch (error: any) { + console.error('Error adding Notion user:', error.message); + } +}; +``` + +### Edit User +```typescript +const editNotionUser = async ( + userId: string, + newSelectedPages: Record, + accessToken: string +) => { + try { + await manageNotionUser( + config, + connectorId, + newSelectedPages, + accessToken, + userId, + 'edit' + ); + console.log('Notion user updated successfully'); + } catch (error: any) { + console.error('Error updating Notion user:', error.message); + } +}; +``` + +### Remove User +```typescript +const removeNotionUser = async (userId: string) => { + try { + await manageNotionUser( + config, + connectorId, + null, + '', + userId, + 'remove' + ); + console.log('Notion user removed successfully'); + } catch (error: any) { + console.error('Error removing Notion user:', error.message); + } +}; +``` + +## Notion User Management API Route + +```typescript +// app/api/manage-notion-user/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { manageNotionUser, VectorizeAPIConfig } from '@vectorize-io/vectorize-connect'; + +export async function POST(request: NextRequest) { + try { + const { connectorId, userId, action, selectedPages, accessToken } = await request.json(); + + if (!connectorId || !userId || !action) { + return NextResponse.json( + { error: "Missing required parameters" }, + { status: 400 } + ); + } + + // Validate required fields for add/edit actions + if ((action === 'add' || action === 'edit') && (!selectedPages || !accessToken)) { + return NextResponse.json( + { error: "selectedPages and accessToken are required for add/edit actions" }, + { status: 400 } + ); + } + + const config: VectorizeAPIConfig = { + organizationId: process.env.VECTORIZE_ORGANIZATION_ID!, + authorization: process.env.VECTORIZE_API_KEY!, + }; + + const response = await manageNotionUser( + config, + connectorId, + action === 'remove' ? null : selectedPages, + action === 'remove' ? '' : accessToken, + userId, + action + ); + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error: any) { + console.error('Error managing Notion user:', error); + return NextResponse.json( + { error: error.message || "Failed to manage Notion user" }, + { status: 500 } + ); + } +} +``` + +## Complete Notion User Management Component + +```typescript +'use client'; + +import { useState } from 'react'; +import { NotionOAuth } from '@vectorize-io/vectorize-connect'; + +interface NotionUser { + id: string; + workspaceName: string; + selectedPages: Record; + accessToken: string; +} + +export default function NotionWhiteLabelUserManager({ connectorId }: { connectorId: string }) { + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const addUser = async () => { + setIsLoading(true); + setError(null); + + try { + const config = { + clientId: process.env.NEXT_PUBLIC_NOTION_CLIENT_ID!, + clientSecret: '', + redirectUri: `${window.location.origin}/api/notion-callback`, + scopes: ['read_content'], + onSuccess: async (selection) => { + // Add user to connector + const response = await fetch("/api/manage-notion-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId: `user_${Date.now()}`, + action: "add", + selectedPages: selection.pages.reduce((acc: any, page: any) => { + acc[page.id] = { + title: page.title, + pageId: page.id, + parentType: page.parentType + }; + return acc; + }, {}), + accessToken: selection.accessToken + }), + }); + + if (!response.ok) { + throw new Error('Failed to add user to connector'); + } + + // Update local state + const newUser: NotionUser = { + id: `user_${Date.now()}`, + workspaceName: selection.workspaceName, + selectedPages: selection.pages.reduce((acc: any, page: any) => { + acc[page.id] = { + title: page.title, + pageId: page.id, + parentType: page.parentType + }; + return acc; + }, {}), + accessToken: selection.accessToken + }; + + setUsers([...users, newUser]); + setIsLoading(false); + }, + onError: (error) => { + setError(error.message); + setIsLoading(false); + } + }; + + NotionOAuth.startOAuth(config); + + } catch (err: any) { + setError(err.message); + setIsLoading(false); + } + }; + + const removeUser = async (userId: string) => { + setIsLoading(true); + setError(null); + + try { + const response = await fetch("/api/manage-notion-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId, + action: "remove" + }), + }); + + if (!response.ok) { + throw new Error('Failed to remove Notion user'); + } + + setUsers(users.filter(user => user.id !== userId)); + + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + const editUser = async (user: NotionUser) => { + setIsLoading(true); + setError(null); + + try { + const config = { + clientId: process.env.NEXT_PUBLIC_NOTION_CLIENT_ID!, + clientSecret: '', + redirectUri: `${window.location.origin}/api/notion-callback`, + scopes: ['read_content'], + onSuccess: async (selection) => { + // Update user in connector + const response = await fetch("/api/manage-notion-user", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + connectorId, + userId: user.id, + action: "edit", + selectedPages: selection.pages.reduce((acc: any, page: any) => { + acc[page.id] = { + title: page.title, + pageId: page.id, + parentType: page.parentType + }; + return acc; + }, {}), + accessToken: selection.accessToken + }), + }); + + if (!response.ok) { + throw new Error('Failed to update user'); + } + + // Update local state + setUsers(users.map(u => u.id === user.id ? { + ...u, + selectedPages: selection.pages.reduce((acc: any, page: any) => { + acc[page.id] = { + title: page.title, + pageId: page.id, + parentType: page.parentType + }; + return acc; + }, {}), + accessToken: selection.accessToken + } : u)); + + setIsLoading(false); + }, + onError: (error) => { + setError(error.message); + setIsLoading(false); + } + }; + + NotionOAuth.startOAuth(config); + + } catch (err: any) { + setError(err.message); + setIsLoading(false); + } + }; + + return ( +
+
+

Notion Users (White-Label)

+ +
+ + {error && ( +
+ {error} +
+ )} + +
+ {users.map((user) => ( +
+
+
{user.workspaceName}
+
+ {Object.keys(user.selectedPages).length} pages selected +
+
+ {Object.values(user.selectedPages).map(page => page.title).join(', ')} +
+
+ +
+ + + +
+
+ ))} +
+
+ ); +} +``` + +## Error Handling + +```typescript +try { + await manageNotionUser(config, connectorId, selectedPages, accessToken, userId, 'add'); +} catch (error) { + if (error.response?.status === 401) { + console.error('Invalid Vectorize API credentials or Notion access token'); + } else if (error.response?.status === 404) { + console.error('Notion connector not found'); + } else if (error.response?.status === 403) { + console.error('Insufficient permissions to manage Notion user'); + } else { + console.error('Notion user management failed:', error.message); + } +} +``` + +## Next Steps + +- [Notion Frontend Implementation](../../frontend-implementation/white-label/notion.md) +- [Notion Testing](../../testing/white-label/notion.md) diff --git a/src/notionOAuth/types/index.ts b/src/notionOAuth/types/index.ts index 1abad84..ca0381a 100644 --- a/src/notionOAuth/types/index.ts +++ b/src/notionOAuth/types/index.ts @@ -52,5 +52,5 @@ export interface NotionPageSelection extends GenericSelection { */ export enum NotionConnectorType { VECTORIZE = "NOTION_OAUTH_MULTI", - WHITE_LABEL = "notion-oauth-white-label" -} \ No newline at end of file + WHITE_LABEL = "NOTION_OAUTH_WHITE_LABEL" +}