Drive API Integration Guide
Overview
This document provides a comprehensive guide for integrating the Drive feature into the Dheyo application. The Drive feature acts as a local file system similar to Google Drive, allowing users to organize files and folders in a hierarchical structure.
🎯 Current State
- ✅ Basic UI with empty state
- ✅ View mode toggle (Grid/List)
- ✅ Navigation link in sidebar beneath "My Creations"
- ❌ No API integration yet
- ❌ No data fetching or state management
🔌 Available Drive APIs
All Drive APIs are available under the /api/v1/drive endpoint and require authentication via bearer token.
1. Root Drive Management
GET /api/v1/drive
Purpose: Get the authenticated user's root folder ID
Authentication: Required (Bearer token)
Response:
{
"root_folder_id": "5JFvPLWsT7"
}
Status Codes:
200- Success401- Unauthorized404- Root folder not found500- Internal server error
Usage: Call this endpoint on page load to initialize the drive and get the starting folder.
2. Folder Operations
POST /api/v1/drive/folders
Purpose: Create a new folder
Authentication: Required (Bearer token)
Request Body:
{
"parent_folder_id": "5JFvPLWsT7",
"name": "my-folder"
}
Response:
{
"folder_id": "8KpQmNxWz3"
}
Status Codes:
201- Folder created successfully400- Invalid folder name or parent folder ID401- Unauthorized403- User does not have write permission on parent folder404- Parent folder not found500- Internal server error
Usage: Implement the "New Folder" button functionality.
GET /api/v1/drive/folders/{folder_id}
Purpose: List contents of a folder with cursor-based pagination
Authentication: Required (Bearer token)
Path Parameters:
folder_id(string, required) - Folder ID (base58 encoded)
Query Parameters:
limit(integer, optional) - Maximum number of items to return (default: 50, max: 100)cursor(string, optional) - Cursor for pagination (base58 encoded HeyoId)
Response:
{
"folder_id": "5JFvPLWsT7",
"items": [
{
"heyo_id": "8KpQmNxWz3",
"heyo_type": "Folder"
},
{
"heyo_id": "9MqRnOyXa4",
"heyo_type": "File"
}
],
"next_cursor": "7JoPlMwVy2"
}
Status Codes:
200- Success400- Invalid folder ID or cursor format401- Unauthorized403- User does not have read permission404- Folder not found500- Internal server error
Usage: Main content display and navigation. Use next_cursor for pagination (null when no more items).
Item Types:
"File"- Regular file"Folder"- Subfolder"GenT2I"- AI generated image"ProjStoryBoard"- Storyboard project- Other content types may appear
DELETE /api/v1/drive/folders/{folder_id}
Purpose: Delete a folder
Authentication: Required (Bearer token)
Path Parameters:
folder_id(string, required) - Folder ID (base58 encoded)
Response: 204 No Content
Status Codes:
204- Folder deleted successfully400- Invalid folder ID format401- Unauthorized403- User does not have delete permission404- Folder not found500- Internal server error
Usage: Delete folder action in context menu.
POST /api/v1/drive/folders/{folder_id}/move
Purpose: Move a folder to a different parent folder
Authentication: Required (Bearer token)
Path Parameters:
folder_id(string, required) - Folder ID to move (base58 encoded)
Request Body:
{
"destination_folder_id": "7JoPlMwVy2"
}
Response:
{
"folder_id": "8KpQmNxWz3",
"parent_folder_id": "7JoPlMwVy2"
}
Status Codes:
200- Success400- Invalid folder ID format or cannot move root folder401- Unauthorized403- User does not have write permission on folder or destination folder404- Folder or destination folder not found500- Internal server error
Usage: Drag-and-drop operations, move folder to different location.
Note: Moving a folder also updates the content_location table.
PUT /api/v1/drive/folders/{folder_id}/upload
Purpose: Upload a file to a folder
Authentication: Required (Bearer token)
Path Parameters:
folder_id(string, required) - Folder ID or use0for user's root folder (base58 encoded)
Query Parameters:
filename(string, required) - Filename for the uploaded filestorage(string, optional) - Storage backend: "r2", "s3", or "local" (defaults to "r2" if available, then "local")
Request Body:
Raw file content as application/octet-stream
Headers:
Content-Type- MIME type of the file (e.g., "image/png", "video/mp4")
Response:
{
"id": "9MqRnOyXa4",
"name": "example.png",
"sha256": "abc123...",
"mime_type": "image/png",
"size": 1048576,
"external_url": "https://...",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
Status Codes:
201- File uploaded successfully400- Empty body, missing filename, or invalid folder ID401- Unauthorized403- User does not have write permission on the folder404- Folder not found409- File with same SHA256 already exists and user has access500- Internal server error
Usage: File upload functionality. The file is streamed while calculating its SHA256 hash.
Important Notes:
- A database record is created with file metadata
- A content_location entry links the file to the folder
- Default user ACL permissions are applied to the file
3. File Operations
GET /api/v1/drive/file/{file_id}
Purpose: Get file metadata by ID
Authentication: Required (Bearer token)
Path Parameters:
file_id(string, required) - File ID (base58 encoded)
Response:
{
"id": "9MqRnOyXa4",
"name": "example.png",
"sha256": "abc123...",
"mime_type": "image/png",
"size": 1048576,
"external_url": "https://...",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
Status Codes:
200- Success400- Invalid file ID format401- Unauthorized403- User does not have read permission404- File not found500- Internal server error
Usage: Get file details for preview, display properties.
GET /api/v1/drive/file/{file_id}/data
Purpose: Download file content from storage (R2)
Authentication: Required (Bearer token)
Path Parameters:
file_id(string, required) - File ID (base58 encoded)
Response: Raw binary file data (application/octet-stream)
Status Codes:
200- Success400- Invalid file ID format401- Unauthorized403- User does not have read permission404- File not found or no R2 URL available500- Internal server error
Usage: File download, file preview generation.
PATCH /api/v1/drive/file/{file_id}
Purpose: Rename a file
Authentication: Required (Bearer token)
Path Parameters:
file_id(string, required) - File ID (base58 encoded)
Request Body:
{
"name": "new-filename.png"
}
Response:
{
"id": "9MqRnOyXa4",
"name": "new-filename.png",
"sha256": "abc123...",
"mime_type": "image/png",
"size": 1048576,
"external_url": "https://...",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:35:00Z"
}
Status Codes:
200- Success400- Invalid file ID format or invalid file name401- Unauthorized403- User does not have write permission404- File not found500- Internal server error
Usage: File rename functionality in context menu.
DELETE /api/v1/drive/file/{file_id}
Purpose: Delete a file (removes user's access)
Authentication: Required (Bearer token)
Path Parameters:
file_id(string, required) - File ID (base58 encoded)
Response: 204 No Content
Status Codes:
204- Success400- Invalid file ID format401- Unauthorized403- User does not have delete permission404- File not found500- Internal server error
Usage: Delete file action in context menu.
Note: This removes the user's ACL entry. The file data remains for other users who have access.
POST /api/v1/drive/file/{file_id}/move
Purpose: Move a file to a different folder
Authentication: Required (Bearer token)
Path Parameters:
file_id(string, required) - File ID (base58 encoded)
Request Body:
{
"destination_folder_id": "7JoPlMwVy2"
}
Response:
{
"file_id": "9MqRnOyXa4",
"folder_id": "7JoPlMwVy2"
}
Status Codes:
200- Success400- Invalid file ID or folder ID format401- Unauthorized403- User does not have write permission on file or destination folder404- File or destination folder not found500- Internal server error
Usage: Drag-and-drop operations, move file to different folder.
📋 Implementation Plan
Phase 1: Core Functionality (MVP)
Step 1: Create Service Layer
Create /UI/divq/src/services/driveApi.ts with these functions:
import { client } from './api'
// Get root folder ID
export const getDriveRoot = async (): Promise<{ root_folder_id: string }> => {
// Implementation
}
// List folder contents
export const listFolder = async (
folderId: string,
limit: number = 50,
cursor?: string
): Promise<{
folder_id: string
items: Array<{ heyo_id: string; heyo_type: string }>
next_cursor: string | null
}> => {
// Implementation
}
// Create new folder
export const createFolder = async (
parentFolderId: string,
name: string
): Promise<{ folder_id: string }> => {
// Implementation
}
// Upload file
export const uploadFile = async (
folderId: string,
file: File,
filename: string
): Promise<DriveFile> => {
// Implementation
}
// Delete folder
export const deleteFolder = async (folderId: string): Promise<void> => {
// Implementation
}
// Delete file
export const deleteFile = async (fileId: string): Promise<void> => {
// Implementation
}
Step 2: Create Type Definitions
Create /UI/divq/src/types/drive.ts:
export interface DriveFile {
id: string
name: string
sha256: string
mime_type: string
size: number
external_url?: string
created_at: string
updated_at: string
}
export interface FolderContent {
heyo_id: string
heyo_type: string // "File", "Folder", "GenT2I", "ProjStoryBoard", etc.
}
export interface ListFolderResponse {
folder_id: string
items: FolderContent[]
next_cursor: string | null
}
export interface FolderPathItem {
id: string
name: string
}
Step 3: Update DrivePage Component
Implement in /UI/divq/src/pages/DrivePage.tsx:
- Fetch root folder on mount
- Display folder contents (files & folders)
- Handle pagination with infinite scroll
- Implement "New Folder" modal
- Implement "Upload Files" functionality
- Show loading states and proper empty states
Step 4: React Query Integration
// Custom hooks for data fetching
export const useDriveRoot = () => {
return useQuery({
queryKey: ['drive', 'root'],
queryFn: getDriveRoot,
staleTime: Infinity, // Root folder doesn't change
})
}
export const useFolderContents = (folderId: string) => {
return useInfiniteQuery({
queryKey: ['drive', 'folder', folderId],
queryFn: ({ pageParam }) => listFolder(folderId, 50, pageParam),
initialPageParam: undefined,
getNextPageParam: (lastPage) => lastPage.next_cursor,
})
}
export const useCreateFolder = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ parentId, name }: { parentId: string; name: string }) =>
createFolder(parentId, name),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['drive', 'folder'] })
},
})
}
export const useUploadFile = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({
folderId,
file,
filename,
}: {
folderId: string
file: File
filename: string
}) => uploadFile(folderId, file, filename),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['drive', 'folder'] })
},
})
}
Phase 2: Enhanced Features
File Operations
-
Rename Files
- Context menu option
- Modal for name input
- PATCH
/api/v1/drive/file/{file_id}
-
Download Files
- Download button/menu option
- GET
/api/v1/drive/file/{file_id}/data - Handle different MIME types
-
File Preview
- Image preview (lightbox)
- Video player
- Use
external_urlor fetch data endpoint
-
Context Menu
- Rename, Move, Delete
- Download, Share
- View Properties
Folder Navigation
-
Breadcrumb Trail
- Clickable path segments
- Store folder path in state
- Navigate to any parent folder
-
Back/Forward Navigation
- Browser history integration
- useNavigate with state
-
Folder Tree Sidebar (Optional)
- Collapsible tree view
- Quick navigation
-
Move Operations
- Drag & drop to move files/folders
- POST
/api/v1/drive/file/{file_id}/move - POST
/api/v1/drive/folders/{folder_id}/move
Advanced Features
-
Search
- Search within current folder
- Global drive search (if API supports)
-
Sorting
- By name, date, size, type
- Ascending/descending
-
Multi-Select
- Checkbox selection
- Batch operations (delete, move)
-
Drag & Drop
- Upload by dropping files
- Move by dragging items
-
File Upload Queue
- Show upload progress
- Multiple simultaneous uploads
- Cancel uploads
🔑 Key Implementation Notes
Authentication
All Drive API calls require authentication. The bearer token is automatically included by the API client:
import { client } from './api'
// Token is handled by the client
Error Handling
Implement proper error handling for common scenarios:
- 401 Unauthorized - Redirect to login
- 403 Forbidden - Show permission error
- 404 Not Found - Handle missing resources
- 409 Conflict - File already exists (deduplication)
- 500 Server Error - Show generic error message
Content Type Handling
The API returns heyo_type to distinguish between different content types:
const renderItem = (item: FolderContent) => {
switch (item.heyo_type) {
case 'File':
return <FileItem id={item.heyo_id} />
case 'Folder':
return <FolderItem id={item.heyo_id} />
case 'GenT2I':
return <GeneratedImageItem id={item.heyo_id} />
case 'ProjStoryBoard':
return <StoryboardItem id={item.heyo_id} />
default:
return <GenericItem id={item.heyo_id} type={item.heyo_type} />
}
}
Pagination Strategy
Implement cursor-based pagination for large folders:
const [folderContents, setFolderContents] = useState<FolderContent[]>([])
const [cursor, setCursor] = useState<string | null>(null)
const [hasMore, setHasMore] = useState(true)
const loadMore = async () => {
const response = await listFolder(currentFolderId, 50, cursor)
setFolderContents([...folderContents, ...response.items])
setCursor(response.next_cursor)
setHasMore(response.next_cursor !== null)
}
File Upload Implementation
The upload endpoint requires raw binary data:
const uploadFile = async (
folderId: string,
file: File,
filename: string
): Promise<DriveFile> => {
const arrayBuffer = await file.arrayBuffer()
const uint8Array = new Uint8Array(arrayBuffer)
const response = await client.drive.uploadFile(folderId, {
filename,
file: uint8Array,
contentType: file.type,
})
return response
}
Breadcrumb Navigation
Track the folder path as users navigate:
const [folderPath, setFolderPath] = useState<FolderPathItem[]>([
{ id: 'root', name: 'My Drive' },
])
const navigateToFolder = (folderId: string, folderName: string) => {
// Add to path or truncate if navigating up
const existingIndex = folderPath.findIndex((f) => f.id === folderId)
if (existingIndex !== -1) {
// Navigating up - truncate path
setFolderPath(folderPath.slice(0, existingIndex + 1))
} else {
// Navigating down - append to path
setFolderPath([...folderPath, { id: folderId, name: folderName }])
}
setCurrentFolderId(folderId)
}
🔒 ACL Permissions
The Drive system uses ACL (Access Control List) permissions:
- USER_READ - View file/folder contents
- USER_WRITE - Modify, move, rename
- USER_DELETE - Delete files/folders
Files and folders automatically get default user ACL permissions when created.
📊 Data Models
File Schema
interface File {
id: string // Base58 encoded HeyoId
name: string // Filename with extension
sha256: string // File hash for deduplication
mime_type: string // e.g., "image/png", "video/mp4"
size: number // File size in bytes
external_url?: string // Public URL if available
created_at: string // ISO 8601 timestamp
updated_at: string // ISO 8601 timestamp
}
Folder Content Schema
interface FolderObject {
heyo_id: string // Base58 encoded content ID
heyo_type: string // "File", "Folder", "GenT2I", etc.
}
🚀 Quick Start Checklist
- Create
driveApi.tsservice file - Create
drive.tstypes file - Implement
getDriveRootAPI call - Implement
listFolderAPI call - Add React Query hooks
- Update DrivePage to fetch data
- Display files and folders
- Implement "New Folder" button
- Implement "Upload Files" button
- Add loading and error states
- Add pagination/infinite scroll
- Test basic CRUD operations
📚 Related Documentation
🐛 Troubleshooting
Common Issues
Issue: "Root folder not found" error
- Solution: Ensure the user's root folder is created during account initialization
Issue: File upload returns 409 Conflict
- Solution: This is expected behavior when the same file (by SHA256) already exists. The user already has access to this file.
Issue: Pagination not working
- Solution: Ensure you're passing the
next_cursorfrom the previous response, not creating your own cursor
Issue: Cannot move folder to itself
- Solution: Add validation to prevent circular references before calling the API
📝 Notes
- The Drive system uses content_location table to track which content is in which folder
- Files are deduplicated by SHA256 hash
- Deleting a file only removes the user's access, not the actual file data
- The system supports multiple storage backends: R2 (default), S3, and local filesystem
- All IDs are base58 encoded for URL-safe representation
🔄 Future Enhancements
- Sharing files/folders with other users
- Folder permissions management
- File versioning
- Trash/recycle bin
- Starred/favorite files
- Recent files view
- Storage quota management
- Collaborative editing
- File comments and annotations
Last Updated: December 2024