# Google Drive Client

Upload, download, and manage files in Google Drive.

## Methods

| Method                                   | Description                                              |
| ---------------------------------------- | -------------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any Google Drive API request with schema validation |

## Usage

### List Files

```typescript
import { api, z, googleDrive } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_DRIVE = "a1b2c3d4-5678-90ab-cdef-gdrive000001";

const FileSchema = z.object({
  id: z.string(),
  name: z.string(),
  mimeType: z.string(),
  parents: z.array(z.string()).optional(),
  createdTime: z.string().optional(),
  modifiedTime: z.string().optional(),
  size: z.string().optional(),
  webViewLink: z.string().optional(),
  webContentLink: z.string().optional(),
  owners: z
    .array(
      z.object({
        displayName: z.string(),
        emailAddress: z.string(),
      }),
    )
    .optional(),
});

const ListFilesResponseSchema = z.object({
  files: z.array(FileSchema),
  nextPageToken: z.string().optional(),
});

export default api({
  integrations: {
    drive: googleDrive(PROD_DRIVE),
  },
  name: "GoogleDriveExample",
  input: z.object({
    folderId: z.string().optional(),
    pageSize: z.number().default(50),
  }),
  output: z.object({
    files: z.array(
      z.object({
        id: z.string(),
        name: z.string(),
        type: z.string(),
      }),
    ),
  }),
  async run(ctx, { folderId, pageSize }) {
    // Build query
    let query = "trashed = false";
    if (folderId) {
      query += ` and '${folderId}' in parents`;
    }

    const result = await ctx.integrations.drive.apiRequest(
      {
        method: "GET",
        path: "/files",
        params: {
          q: query,
          pageSize: pageSize,
          fields: "files(id,name,mimeType,modifiedTime,size),nextPageToken",
          orderBy: "modifiedTime desc",
        },
      },
      { response: ListFilesResponseSchema },
    );

    return {
      files: result.files.map((f) => ({
        id: f.id,
        name: f.name,
        type: f.mimeType,
      })),
    };
  },
});
```

### Get File Metadata

```typescript
const file = await ctx.integrations.drive.apiRequest(
  {
    method: "GET",
    path: `/files/${fileId}`,
    params: {
      fields:
        "id,name,mimeType,size,createdTime,modifiedTime,parents,webViewLink,webContentLink,owners",
    },
  },
  { response: FileSchema },
);

console.log(`File: ${file.name} (${file.size} bytes)`);
```

### Download File Content

```typescript
const DownloadResponseSchema = z.string(); // Raw content

// For text files
const content = await ctx.integrations.drive.apiRequest(
  {
    method: "GET",
    path: `/files/${fileId}`,
    params: {
      alt: "media",
    },
  },
  { response: DownloadResponseSchema },
);

// For Google Docs, export to a format
const ExportResponseSchema = z.string();

const docContent = await ctx.integrations.drive.apiRequest(
  {
    method: "GET",
    path: `/files/${fileId}/export`,
    params: {
      mimeType: "text/plain", // or application/pdf, text/html, etc.
    },
  },
  { response: ExportResponseSchema },
);
```

### Create a Folder

```typescript
const folder = await ctx.integrations.drive.apiRequest(
  {
    method: "POST",
    path: "/files",
    body: {
      name: "New Folder",
      mimeType: "application/vnd.google-apps.folder",
      parents: [parentFolderId], // Optional: specify parent folder
    },
  },
  { response: FileSchema },
);

console.log(`Created folder: ${folder.id}`);
```

### Upload a File

```typescript
// For simple uploads (< 5MB)
const uploadedFile = await ctx.integrations.drive.apiRequest(
  {
    method: "POST",
    path: "/upload/drive/v3/files",
    params: {
      uploadType: "multipart",
    },
    body: {
      metadata: {
        name: "report.txt",
        parents: [folderId],
      },
      media: {
        mimeType: "text/plain",
        body: "File content here",
      },
    },
  },
  { response: FileSchema },
);
```

### Update File Metadata

```typescript
const updatedFile = await ctx.integrations.drive.apiRequest(
  {
    method: "PATCH",
    path: `/files/${fileId}`,
    body: {
      name: "Renamed File.txt",
      description: "Updated description",
    },
  },
  { response: FileSchema },
);
```

### Move File to Another Folder

```typescript
const movedFile = await ctx.integrations.drive.apiRequest(
  {
    method: "PATCH",
    path: `/files/${fileId}`,
    params: {
      addParents: newFolderId,
      removeParents: oldFolderId,
    },
  },
  { response: FileSchema },
);
```

### Search Files

```typescript
const result = await ctx.integrations.drive.apiRequest(
  {
    method: "GET",
    path: "/files",
    params: {
      q: "name contains 'report' and mimeType = 'application/pdf' and trashed = false",
      pageSize: 25,
      fields: "files(id,name,mimeType,modifiedTime)",
    },
  },
  { response: ListFilesResponseSchema },
);
```

### Share a File

```typescript
const PermissionSchema = z.object({
  id: z.string(),
  type: z.string(),
  role: z.string(),
  emailAddress: z.string().optional(),
});

const permission = await ctx.integrations.drive.apiRequest(
  {
    method: "POST",
    path: `/files/${fileId}/permissions`,
    body: {
      type: "user", // user, group, domain, anyone
      role: "reader", // reader, writer, commenter, owner
      emailAddress: "colleague@example.com",
    },
    params: {
      sendNotificationEmail: true,
    },
  },
  { response: PermissionSchema },
);
```

### Delete a File

```typescript
// Move to trash
await ctx.integrations.drive.apiRequest(
  {
    method: "PATCH",
    path: `/files/${fileId}`,
    body: {
      trashed: true,
    },
  },
  { response: FileSchema },
);

// Permanently delete
await ctx.integrations.drive.apiRequest(
  {
    method: "DELETE",
    path: `/files/${fileId}`,
  },
  { response: z.void() },
);
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await drive.listFiles({ ... });
await drive.uploadFile({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.drive.apiRequest(
  { method: "GET", path: "/files", params: { ... } },
  { response: ListFilesResponseSchema }
);
```

### Fields Parameter Required

By default, only basic fields are returned. Use the `fields` parameter:

```typescript
// WRONG - Missing fields, gets minimal data
const file = await ctx.integrations.drive.apiRequest(
  { method: "GET", path: `/files/${fileId}` },
  { response: FileSchema },
);
// file.size, file.owners, etc. will be undefined

// CORRECT - Specify needed fields
const file = await ctx.integrations.drive.apiRequest(
  {
    method: "GET",
    path: `/files/${fileId}`,
    params: {
      fields: "id,name,mimeType,size,createdTime,modifiedTime,owners",
    },
  },
  { response: FileSchema },
);
```

### Query Syntax

Google Drive uses a specific query syntax:

```typescript
// String comparison (use single quotes)
const q = "name = 'Report.pdf'";
const q = "name contains 'report'";

// MIME type filtering
const q = "mimeType = 'application/pdf'";
const q = "mimeType = 'application/vnd.google-apps.folder'"; // Folders

// Trash filtering
const q = "trashed = false";

// Parent folder
const q = "'folder-id-here' in parents";

// Combining conditions
const q =
  "name contains 'report' and mimeType = 'application/pdf' and trashed = false";

// Date filtering
const q = "modifiedTime > '2024-01-01T00:00:00'";
```

### Google Docs Export vs Download

Google Docs, Sheets, etc. must be exported, not downloaded directly:

```typescript
// Regular files - use alt=media
const path = `/files/${fileId}?alt=media`;

// Google Docs - use export endpoint
const path = `/files/${fileId}/export?mimeType=text/plain`;

// Export formats for Google Docs
// Documents: text/plain, text/html, application/pdf, application/vnd.oasis.opendocument.text
// Spreadsheets: text/csv, application/pdf, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
// Presentations: application/pdf, application/vnd.openxmlformats-officedocument.presentationml.presentation
```

### File Size as String

File sizes are returned as strings:

```typescript
// WRONG
const schema = z.object({ size: z.number() });

// CORRECT
const schema = z.object({ size: z.string() });

// Convert when using
const sizeBytes = parseInt(file.size, 10);
const sizeMB = sizeBytes / (1024 * 1024);
```

### Upload Path vs Regular Path

The base URL already includes `/drive/v3`, so regular API paths omit this prefix. However, uploads use a different URL root (`/upload/drive/v3/...` instead of `/drive/v3/...`) and require the full path:

```typescript
// Regular API calls (base URL already has /drive/v3)
const path = "/files";

// Upload calls use a different URL root — include the full path
const path = "/upload/drive/v3/files";

// WRONG - Duplicates base URL prefix for regular calls
const path = "/drive/v3/files";
```

### Folder MIME Type

Folders have a special MIME type:

```typescript
const FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";

// Create folder
const body = {
  name: "New Folder",
  mimeType: FOLDER_MIME_TYPE,
};

// Filter for folders only
const q = `mimeType = '${FOLDER_MIME_TYPE}'`;
```

### Pagination

Use pageToken for large result sets:

```typescript
async function getAllFiles(drive: GoogleDriveClient, folderId: string) {
  const allFiles: File[] = [];
  let pageToken: string | undefined;

  while (true) {
    const result = await ctx.integrations.drive.apiRequest(
      {
        method: "GET",
        path: "/files",
        params: {
          q: `'${folderId}' in parents and trashed = false`,
          pageSize: 100,
          fields: "files(id,name,mimeType),nextPageToken",
          ...(pageToken && { pageToken }),
        },
      },
      { response: ListFilesResponseSchema },
    );

    allFiles.push(...result.files);
    if (!result.nextPageToken) break;
    pageToken = result.nextPageToken;
  }

  return allFiles;
}
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.drive.apiRequest(
    { method: "GET", path: "/files", params: { ... } },
    { response: ListFilesResponseSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [Google Drive API Documentation](https://developers.google.com/drive/api/reference/rest/v3)
- [Files Resource](https://developers.google.com/drive/api/reference/rest/v3/files)
- [Query String Syntax](https://developers.google.com/drive/api/guides/search-files)
- [Upload Files](https://developers.google.com/drive/api/guides/manage-uploads)
