Complete Documentation for Media File Upload and Management Endpoints
This router handles file upload operations including uploading files from URLs or direct file uploads to Google Cloud Storage, and deleting uploaded files.
The File Upload router provides comprehensive media file management functionality including:
- File Upload: Upload files directly or from URLs to Google Cloud Storage
- File Deletion: Delete files from Google Cloud Storage
- Multiple Formats: Support for images, documents, videos, and other file types
- Size Limits: 15MB maximum file size
- Automatic Processing: Automatic file type detection and content type handling
Base Path: /{MODE}/upload-media, /{MODE}/delete-media
Authentication: All endpoints require authentication
Permissions:
- Upload:
add_uploadpermission - Delete:
delete_uploadpermission
Endpoint: POST /{MODE}/upload-media
Description: Upload a media file either from URL or direct file upload to Google Cloud Storage.
Authentication: Required (access_token or session_token)
Required Permission: add_upload
Request Headers:
Authorization: Bearer <access_token>
Content-Type: multipart/form-data (for file upload)
# OR
Content-Type: application/x-www-form-urlencoded (for URL upload)
# OR
X-Session-Token: <session_token>
Request Options:
Option 1: Direct File Upload (Multipart Form Data)
POST /dev/v1/upload-media HTTP/1.1
Authorization: Bearer <token>
Content-Type: multipart/form-data
file: [binary file data]Option 2: Upload from URL (Form Data)
POST /dev/v1/upload-media HTTP/1.1
Authorization: Bearer <token>
Content-Type: application/x-www-form-urlencoded
url: https://example.com/image.jpgResponse:
{
"success": true,
"message": "File uploaded successfully",
"data": {
"url": "https://storage.googleapis.com/bucket-name/media/users/user-uuid-file.jpg"
}
}Workflow (Direct File Upload):
1. Authenticated Request
│
├─► Validate JWT Token
│
├─► Check Permission (add_upload)
│
├─► Validate Request
│ └─► Check file or url exists
│
├─► Process File Upload
│ ├─► Extract file buffer
│ ├─► Get file extension from filename
│ └─► Get content type from mimetype
│
├─► Generate Object Key
│ └─► {userId}-|-{uuid}.{extension}
│
├─► Upload to Google Cloud Storage
│ ├─► Folder: media/users
│ ├─► Object Key: generated key
│ └─► uploadToGoogleStorageFromString()
│
└─► Return Public URL
Workflow (Upload from URL):
1. Authenticated Request
│
├─► Validate JWT Token
│
├─► Check Permission (add_upload)
│
├─► Validate Request
│ └─► Check url exists
│
├─► Download File from URL
│ ├─► axios.get(url, { responseType: 'arraybuffer' })
│ ├─► Max size: 15MB
│ └─► Convert to Buffer
│
├─► Extract File Information
│ ├─► Extension from URL or Content-Type header
│ └─► Content type from response headers
│
├─► Generate Object Key
│ └─► {userId}-|-{uuid}.{extension}
│
├─► Upload to Google Cloud Storage
│ └─► uploadToGoogleStorageFromString()
│
└─► Return Public URL
Supported File Types:
- Images: jpg, jpeg, png, gif, webp, svg
- Documents: pdf, doc, docx, txt
- Videos: mp4, avi, mov, webm
- Other: Based on content type
File Size Limit: 15MB maximum
Storage Location:
- Folder:
media/users - Object Key Format:
{userId}-|-{uuid}.{extension} - Public URL:
https://storage.googleapis.com/{bucket-name}/media/users/{object-key}
Client-Server Communication Flow (Direct File Upload):
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT SIDE │
└─────────────────────────────────────────────────────────────────┘
Step 1: User selects file
├─► User clicks "Choose File" button
├─► File picker opens
└─► User selects file (image, document, video, etc.)
Step 2: Client validates file
├─► Check file size (must be < 15MB)
├─► Check file type (optional validation)
├─► Optionally: Preview file before upload
└─► Show upload progress indicator
Step 3: Client prepares request
├─► Create FormData object
├─► Append file to FormData with key "file"
├─► Retrieve stored access_token or session_token
└─► Prepare POST request with multipart/form-data
Step 4: Client sends request
POST /{MODE}/upload-media
Headers:
Authorization: Bearer <token>
Content-Type: multipart/form-data
Body:
file: <File object>
Step 5: Client receives response
├─► Success (200): Display uploaded file URL, show success message
├─► Bad Request (400): Show validation error
├─► Unauthorized (401): Redirect to login
├─► Forbidden (403): Show permission error
├─► Payload Too Large (413): Show file size error
└─► Error (500): Show error message
Step 6: Client processes response
├─► Extract public URL from response.data.url
├─► Store URL for later use
├─► Display uploaded file (if image, show preview)
└─► Update UI with uploaded file information
┌─────────────────────────────────────────────────────────────────┐
│ SERVER SIDE │
└─────────────────────────────────────────────────────────────────┘
Step 1: Request validation
├─► Extract token from headers
├─► Validate token (JWT verification)
├─► Check token blacklist
├─► Extract user from token
├─► Check permission: "add_upload"
└─► Validate request has either file or url
Step 2: File processing (if file provided)
├─► Read file data from request
├─► Extract file extension from filename
├─► Get content type from file.content_type
└─► Generate unique object key: {userId}-|-{uuid}.{extension}
Step 3: URL processing (if url provided)
├─► Download file from URL using httpx
├─► Check response status (raise_for_status)
├─► Get file data from response.content
├─► Extract extension from URL or Content-Type header
├─► Get content type from response headers
└─► Generate unique object key: {userId}-|-{uuid}.{extension}
Step 4: Upload to Google Cloud Storage
├─► Set folder: "media/users"
├─► Upload file using upload_to_google_storage_from_string()
├─► Set content type
├─► Make file publicly accessible
└─► Get public URL
Step 5: Response preparation
├─► Build SUCCESS response
├─► Include public URL in data.url
└─► Return response with user's language preference
Step 6: Error handling
├─► No file or URL: Return 400
├─► File too large: Return 413
├─► Download error: Return 500
├─► Upload error: Return 500
├─► Permission denied: Return 403
└─► Token invalid: Return 401
Client-Server Communication Flow (Upload from URL):
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT SIDE │
└─────────────────────────────────────────────────────────────────┘
Step 1: User enters URL
├─► User enters file URL in input field
├─► User clicks "Upload from URL" button
└─► Client validates URL format
Step 2: Client validates URL
├─► Check URL format (http:// or https://)
├─► Optionally: Preview file from URL
└─► Show loading indicator
Step 3: Client sends request
POST /{MODE}/upload-media
Headers:
Authorization: Bearer <token>
Content-Type: application/x-www-form-urlencoded
Body:
url: https://example.com/image.jpg
Step 4: Client receives response
├─► Success (200): Display uploaded file URL
├─► Bad Request (400): Show validation error
├─► Unauthorized (401): Redirect to login
├─► Forbidden (403): Show permission error
└─► Error (500): Show error message
┌─────────────────────────────────────────────────────────────────┐
│ SERVER SIDE │
└─────────────────────────────────────────────────────────────────┘
Step 1: Request validation
├─► Extract token from headers
├─► Validate token
├─► Extract user from token
├─► Check permission: "add_upload"
└─► Validate url parameter exists
Step 2: Download file from URL
├─► Create httpx AsyncClient
├─► GET request to URL with responseType: 'arraybuffer'
├─► Check response status
├─► Get file data from response.content
└─► Handle download errors
Step 3: Extract file information
├─► Get extension from URL (split by '.')
├─► If no extension, get from Content-Type header
├─► Get content type from response headers
└─► Generate unique object key: {userId}-|-{uuid}.{extension}
Step 4: Upload to Google Cloud Storage
├─► Set folder: "media/users"
├─► Upload file using upload_to_google_storage_from_string()
└─► Get public URL
Step 5: Response preparation
├─► Build SUCCESS response
├─► Include public URL in data.url
└─► Return response
Step 6: Error handling
├─► Invalid URL: Return 400
├─► Download failed: Return 500
├─► Upload error: Return 500
└─► Permission denied: Return 403
Error Responses:
// 400 - No File or URL
{
"success": false,
"id": null,
"message": "File or URL required",
"error": {
"code": "MEDIA_FILE_OR_URL",
"details": {
"user_id": "uuid",
"message": "Either file or url must be provided"
}
}
}
// 403 - Permission Denied
{
"success": false,
"id": null,
"message": "Permission denied",
"error": {
"code": "PERMISSION_DENIED",
"details": {
"required_permission": "add_upload"
}
}
}
// 500 - Upload Error
{
"success": false,
"id": null,
"message": "Upload processing error",
"error": {
"code": "MEDIA_UPLOADING_PROCESSING_ERROR",
"details": {
"user_id": "uuid",
"exception": "Error details"
}
}
}Endpoint: DELETE /{MODE}/delete-media
Description: Delete a media file from Google Cloud Storage.
Authentication: Required (access_token or session_token)
Required Permission: delete_upload
Query Parameters:
url(required): GCS URL of the file to delete
Request Headers:
Authorization: Bearer <access_token>
# OR
X-Session-Token: <session_token>
Request Example:
DELETE /dev/v1/delete-media?url=https://storage.googleapis.com/bucket-name/media/users/user-uuid-file.jpg HTTP/1.1
Authorization: Bearer <token>Response:
{
"success": true,
"message": "File deleted successfully from bucket 'bucket-name'",
"data": {
"bucket": "bucket-name"
}
}Workflow:
1. Authenticated Request
│
├─► Validate JWT Token
│
├─► Check Permission (delete_upload)
│
├─► Validate Request
│ └─► Check url parameter exists
│
├─► Validate Bucket Configuration
│ └─► Check GOOGLE_STORAGE_BUCKET_NAME exists
│
├─► Parse GCS URL
│ ├─► Decode URL
│ ├─► Validate domain (storage.googleapis.com)
│ └─► Extract bucket and object path
│
├─► Validate Bucket Match
│ └─► Ensure URL bucket matches configured bucket
│
├─► Extract Object Path
│ ├─► Folder: media/users (or other)
│ └─► Object Key: file name
│
├─► Delete from Google Cloud Storage
│ └─► deleteFromGoogleStorage(folder, objectKey)
│
└─► Return Success Response
URL Format:
https://storage.googleapis.com/{bucket-name}/{folder}/{object-key}
Example:
https://storage.googleapis.com/my-bucket/media/users/user-123-file.jpg
Validation:
- URL must be from
storage.googleapis.com - Bucket in URL must match configured bucket
- URL must have valid path structure
Client-Server Communication Flow:
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT SIDE │
└─────────────────────────────────────────────────────────────────┘
Step 1: User initiates deletion
├─► User clicks "Delete" button on file
├─► Client shows confirmation dialog
└─► User confirms deletion
Step 2: Client prepares request
├─► Get GCS URL of file to delete
├─► URL encode the URL parameter
├─► Retrieve stored access_token or session_token
└─► Prepare DELETE request with query parameter
Step 3: Client sends request
DELETE /{MODE}/delete-media?url={encoded_url}
Headers:
Authorization: Bearer <token>
# OR
X-Session-Token: <session_token>
Step 4: Client receives response
├─► Success (200): Remove file from UI, show success message
├─► Bad Request (400): Show validation error
├─► Unauthorized (401): Redirect to login
├─► Forbidden (403): Show permission/bucket error
├─► Not Found (404): Show file not found error
└─► Error (500): Show error message
Step 5: Client processes response
├─► Remove file from display
├─► Update file list
└─► Clear file URL from storage
┌─────────────────────────────────────────────────────────────────┐
│ SERVER SIDE │
└─────────────────────────────────────────────────────────────────┘
Step 1: Request validation
├─► Extract token from headers
├─► Validate token (JWT verification)
├─► Check token blacklist
├─► Extract user from token
├─► Extract url query parameter
├─► Check permission: "delete_upload"
└─► Validate url parameter exists
Step 2: Validate bucket configuration
├─► Get GOOGLE_STORAGE_BUCKET_NAME from environment
└─► If not configured: Return 500
Step 3: Parse and validate GCS URL
├─► Decode URL (unquote)
├─► Parse URL using urlparse
├─► Validate domain contains "storage.googleapis.com"
├─► Extract bucket and object path from URL
├─► Validate URL format (bucket/path structure)
└─► Check if URL bucket matches configured bucket
Step 4: Validate bucket match
├─► Compare URL bucket with configured bucket
└─► If mismatch: Return 403 Forbidden
Step 5: Check file existence
├─► Get blob from Google Cloud Storage
├─► Check if blob.exists()
└─► If not exists: Return 404 Not Found
Step 6: Delete file
├─► Delete blob from Google Cloud Storage
└─► Verify deletion succeeded
Step 7: Response preparation
├─► Build SUCCESS response
├─► Include bucket name in data.bucket
└─► Return response with user's language preference
Step 8: Error handling
├─► No URL: Return 400
├─► Invalid URL format: Return 400
├─► Bucket mismatch: Return 403
├─► File not found: Return 404
├─► Delete error: Return 500
├─► Permission denied: Return 403
└─► Token invalid: Return 401
Error Responses:
// 400 - No URL Provided
{
"success": false,
"message": "No URL provided",
"statusCode": 400
}
// 400 - Invalid GCS URL
{
"success": false,
"message": "Invalid GCS URL",
"statusCode": 400
}
// 403 - Bucket Mismatch
{
"success": false,
"message": "URL bucket does not match configured bucket",
"statusCode": 403
}
// 404 - File Not Found
{
"success": false,
"message": "File not found in GCS",
"statusCode": 404
}
// 500 - Delete Error
{
"success": false,
"id": null,
"message": "Delete processing error",
"error": {
"code": "MEDIA_DELETING_PROCESSING_ERROR",
"details": {
"user_id": "uuid",
"exception": "Error details"
}
}
}┌─────────────────────────────────────────────────────────────┐
│ File Upload Flow │
└────────────────────────────┬────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Direct Upload │ │ Upload from │
│ (Multipart) │ │ URL │
└───────┬───────┘ └───────┬───────┘
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Extract File │ │ Download File │
│ Buffer │ │ from URL │
└───────┬───────┘ └───────┬───────┘
│ │
└─────────────┬─────────────┘
│
▼
┌─────────────────┐
│ Get File Info │
│ (ext, type) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Generate Key │
│ {userId}-uuid.ext│
└────────┬────────┘
│
▼
┌─────────────────┐
│ Upload to GCS │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Return Public │
│ URL │
└─────────────────┘
1. User Requests File Deletion
│
├─► Validate Authentication
│ └─► Check JWT token
│
├─► Check Permission
│ └─► delete_upload permission
│
├─► Validate URL Parameter
│ └─► URL must be provided
│
├─► Parse GCS URL
│ ├─► Decode URL
│ ├─► Validate domain
│ └─► Extract bucket and path
│
├─► Validate Bucket
│ └─► URL bucket must match config
│
├─► Extract Object Path
│ ├─► Folder name
│ └─► Object key
│
├─► Delete from GCS
│ └─► deleteFromGoogleStorage()
│
└─► Return Success Response
400 Bad Request - No File or URL:
{
"success": false,
"message": "File or URL required",
"error": "Either file or url must be provided",
"statusCode": 400
}400 Bad Request - Invalid URL:
{
"success": false,
"message": "Invalid URL",
"error": "URL must be from storage.googleapis.com",
"statusCode": 400
}401 Unauthorized:
{
"success": false,
"message": "Authentication required",
"error": "Invalid or missing token",
"statusCode": 401
}403 Forbidden:
{
"success": false,
"message": "Permission denied",
"error": "Insufficient permissions. Requires add_upload permission",
"statusCode": 403
}404 Not Found - File Not Found:
{
"success": false,
"message": "File not found",
"error": "File not found in Google Cloud Storage",
"statusCode": 404
}413 Payload Too Large:
{
"success": false,
"message": "File too large",
"error": "File size exceeds 15MB limit",
"statusCode": 413
}500 Internal Server Error - Download Error:
{
"success": false,
"message": "Download error",
"error": "Failed to download file from URL",
"statusCode": 500
}500 Internal Server Error - Upload Error:
{
"success": false,
"message": "Upload processing error",
"error": "Failed to upload file to Google Cloud Storage",
"statusCode": 500
}- File Size: Always validate file size before upload (max 15MB)
- File Types: Validate file types based on your application requirements
- Content Type: Set appropriate content type for proper file handling
- URL Validation: Validate URLs before downloading
- Error Handling: Handle download and upload errors gracefully
- Security: Only allow deletion of files from your configured bucket
- Object Keys: Use unique object keys to prevent overwrites
- Cleanup: Implement cleanup for failed uploads
- CORS: Configure CORS on GCS bucket for web uploads
- Permissions: Use IAM roles for GCS access, not service account keys in code
Required Environment Variables:
GOOGLE_STORAGE_BUCKET_NAME: Name of the GCS bucket
Required Credentials:
- Google Cloud Service Account JSON file
- Proper IAM permissions for bucket access
Storage Structure:
bucket-name/
└── media/
└── users/
├── user-123-uuid1.jpg
├── user-123-uuid2.png
└── user-456-uuid3.pdf
Public URL Format:
https://storage.googleapis.com/{bucket-name}/media/users/{object-key}
- Authentication: All endpoints require authentication
- Authorization: Check permissions before allowing upload/delete
- Bucket Validation: Ensure files are only deleted from your bucket
- URL Validation: Validate URLs to prevent SSRF attacks
- File Type Validation: Validate file types to prevent malicious uploads
- Size Limits: Enforce size limits to prevent DoS attacks
- Content Scanning: Consider scanning uploaded files for malware
- Access Control: Use GCS IAM for fine-grained access control
- File Size: Large files may take time to upload/download
- Concurrent Uploads: Handle multiple concurrent uploads efficiently
- Streaming: For large files, consider streaming uploads
- Caching: Cache public URLs if files don't change
- CDN: Consider using CDN for frequently accessed files
- Compression: Compress images before upload for better performance
// Get authentication token (prefer session_token)
function getAuthToken() {
return localStorage.getItem('session_token') ||
localStorage.getItem('access_token');
}
// API request helper for upload endpoints
async function uploadRequest(url, options = {}) {
const token = getAuthToken();
const headers = {
...options.headers
};
// Prefer X-Session-Token header
if (token) {
headers['X-Session-Token'] = token;
// Or use Authorization header
// headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(url, {
...options,
headers
});
if (response.status === 401) {
// Token expired, redirect to login
window.location.href = '/login';
return null;
}
if (response.status === 403) {
// Permission denied
throw new Error('You do not have permission to upload files');
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error?.message || 'Upload failed');
}
return data;
}// Upload file directly
async function uploadFile(file) {
try {
// Validate file size (15MB limit)
const maxSize = 15 * 1024 * 1024; // 15MB in bytes
if (file.size > maxSize) {
throw new Error('File size exceeds 15MB limit');
}
// Validate file type (optional)
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (!allowedTypes.includes(file.type)) {
throw new Error('File type not allowed');
}
// Create FormData
const formData = new FormData();
formData.append('file', file);
// Show upload progress
showUploadProgress(0);
// Upload file
const response = await uploadRequest('/api/v1/upload-media', {
method: 'POST',
body: formData
});
if (response && response.data) {
showUploadProgress(100);
showNotification('File uploaded successfully');
return response.data.url;
}
} catch (error) {
console.error('Error uploading file:', error);
showError(error.message || 'Failed to upload file');
throw error;
}
}
// Upload with progress tracking
async function uploadFileWithProgress(file, onProgress) {
try {
const formData = new FormData();
formData.append('file', file);
const token = getAuthToken();
const xhr = new XMLHttpRequest();
// Track upload progress
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
if (onProgress) {
onProgress(percentComplete);
}
}
});
return new Promise((resolve, reject) => {
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
if (response.success) {
resolve(response.data.url);
} else {
reject(new Error(response.error?.message || 'Upload failed'));
}
} else {
reject(new Error(`Upload failed with status ${xhr.status}`));
}
});
xhr.addEventListener('error', () => {
reject(new Error('Upload failed'));
});
xhr.open('POST', '/api/v1/upload-media');
xhr.setRequestHeader('X-Session-Token', token);
xhr.send(formData);
});
} catch (error) {
console.error('Error uploading file:', error);
throw error;
}
}// Upload file from URL
async function uploadFromURL(fileUrl) {
try {
// Validate URL format
if (!fileUrl || !fileUrl.startsWith('http://') && !fileUrl.startsWith('https://')) {
throw new Error('Invalid URL');
}
// Create form data
const formData = new FormData();
formData.append('url', fileUrl);
// Show loading
showLoading('Downloading file from URL...');
// Upload from URL
const response = await uploadRequest('/api/v1/upload-media', {
method: 'POST',
body: formData
});
hideLoading();
if (response && response.data) {
showNotification('File uploaded successfully from URL');
return response.data.url;
}
} catch (error) {
hideLoading();
console.error('Error uploading from URL:', error);
showError(error.message || 'Failed to upload from URL');
throw error;
}
}// Delete file from Google Cloud Storage
async function deleteFile(gcsUrl) {
try {
if (!gcsUrl) {
throw new Error('File URL is required');
}
// Confirm deletion
const confirmed = confirm('Are you sure you want to delete this file?');
if (!confirmed) {
return;
}
// Encode URL for query parameter
const encodedUrl = encodeURIComponent(gcsUrl);
const token = getAuthToken();
const response = await fetch(
`/api/v1/delete-media?url=${encodedUrl}`,
{
method: 'DELETE',
headers: {
'X-Session-Token': token
}
}
);
const data = await response.json();
if (response.status === 200 && data.success) {
showNotification('File deleted successfully');
return true;
} else {
throw new Error(data.error?.message || 'Failed to delete file');
}
} catch (error) {
console.error('Error deleting file:', error);
showError(error.message || 'Failed to delete file');
throw error;
}
}// Complete File Upload Manager Class
class FileUploadManager {
constructor() {
this.uploadedFiles = [];
this.maxFileSize = 15 * 1024 * 1024; // 15MB
this.allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf'];
}
async init() {
// Set up file input listeners
const fileInput = document.getElementById('file-input');
if (fileInput) {
fileInput.addEventListener('change', (e) => {
this.handleFileSelect(e.target.files[0]);
});
}
// Set up URL upload form
const urlForm = document.getElementById('url-upload-form');
if (urlForm) {
urlForm.addEventListener('submit', (e) => {
e.preventDefault();
const url = document.getElementById('file-url').value;
this.uploadFromURL(url);
});
}
}
async handleFileSelect(file) {
try {
// Validate file
if (!this.validateFile(file)) {
return;
}
// Show preview if image
if (file.type.startsWith('image/')) {
this.showImagePreview(file);
}
// Upload file
const url = await this.uploadFile(file);
// Add to uploaded files list
this.uploadedFiles.push({
name: file.name,
url: url,
size: file.size,
type: file.type
});
// Update UI
this.renderUploadedFiles();
} catch (error) {
console.error('Error handling file:', error);
showError(error.message);
}
}
validateFile(file) {
// Check file size
if (file.size > this.maxFileSize) {
showError(`File size exceeds ${this.maxFileSize / 1024 / 1024}MB limit`);
return false;
}
// Check file type
if (this.allowedTypes.length > 0 && !this.allowedTypes.includes(file.type)) {
showError('File type not allowed');
return false;
}
return true;
}
showImagePreview(file) {
const reader = new FileReader();
reader.onload = (e) => {
const preview = document.getElementById('image-preview');
if (preview) {
preview.src = e.target.result;
preview.style.display = 'block';
}
};
reader.readAsDataURL(file);
}
async uploadFile(file) {
try {
showLoading('Uploading file...');
const url = await uploadFileWithProgress(file, (progress) => {
this.updateUploadProgress(progress);
});
hideLoading();
showNotification('File uploaded successfully');
return url;
} catch (error) {
hideLoading();
throw error;
}
}
async uploadFromURL(fileUrl) {
try {
showLoading('Uploading from URL...');
const url = await uploadFromURL(fileUrl);
hideLoading();
showNotification('File uploaded successfully from URL');
// Add to uploaded files
this.uploadedFiles.push({
name: fileUrl.split('/').pop(),
url: url,
type: 'url'
});
this.renderUploadedFiles();
return url;
} catch (error) {
hideLoading();
throw error;
}
}
async deleteFile(gcsUrl) {
try {
await deleteFile(gcsUrl);
// Remove from uploaded files list
this.uploadedFiles = this.uploadedFiles.filter(f => f.url !== gcsUrl);
// Update UI
this.renderUploadedFiles();
} catch (error) {
console.error('Error deleting file:', error);
throw error;
}
}
updateUploadProgress(percent) {
const progressBar = document.getElementById('upload-progress');
if (progressBar) {
progressBar.style.width = `${percent}%`;
progressBar.textContent = `${Math.round(percent)}%`;
}
}
renderUploadedFiles() {
const container = document.getElementById('uploaded-files');
if (!container) return;
if (this.uploadedFiles.length === 0) {
container.innerHTML = '<p>No files uploaded yet</p>';
return;
}
container.innerHTML = this.uploadedFiles.map((file, index) => `
<div class="uploaded-file-item">
<div class="file-info">
<h4>${file.name}</h4>
<p>${file.type}</p>
<p>${(file.size / 1024).toFixed(2)} KB</p>
</div>
<div class="file-actions">
<a href="${file.url}" target="_blank" class="btn-view">View</a>
<button onclick="fileUploadManager.deleteFile('${file.url}')" class="btn-delete">Delete</button>
</div>
</div>
`).join('');
}
}
// Initialize file upload manager
document.addEventListener('DOMContentLoaded', () => {
window.fileUploadManager = new FileUploadManager();
fileUploadManager.init();
});// Drag and drop file upload
function setupDragAndDrop() {
const dropZone = document.getElementById('drop-zone');
if (!dropZone) return;
// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// Highlight drop zone when item is dragged over it
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, unhighlight, false);
});
function highlight(e) {
dropZone.classList.add('drag-over');
}
function unhighlight(e) {
dropZone.classList.remove('drag-over');
}
// Handle dropped files
dropZone.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 0) {
fileUploadManager.handleFileSelect(files[0]);
}
}
}
// Initialize drag and drop
setupDragAndDrop();// Image preview with cropping option
async function uploadImageWithPreview(file) {
try {
// Show image preview
const reader = new FileReader();
reader.onload = (e) => {
const preview = document.getElementById('image-preview');
if (preview) {
preview.src = e.target.result;
preview.style.display = 'block';
// Optionally integrate with image cropping library
// Example: Cropper.js
const cropper = new Cropper(preview, {
aspectRatio: 1,
viewMode: 1
});
// Get cropped image on upload
document.getElementById('upload-btn').addEventListener('click', () => {
cropper.getCroppedCanvas().toBlob((blob) => {
uploadFile(blob);
});
});
}
};
reader.readAsDataURL(file);
} catch (error) {
console.error('Error previewing image:', error);
showError('Failed to preview image');
}
}This documentation provides comprehensive coverage of all file upload and management endpoints:
- ✅ Upload Media - Upload files directly or from URL
- ✅ Delete Media - Delete files from Google Cloud Storage
- ✅ Overview and system architecture
- ✅ Detailed endpoint documentation with examples
- ✅ Client-server communication flows
- ✅ Complete workflows and diagrams
- ✅ Error handling guide
- ✅ Best practices and security considerations
- ✅ Complete client-side implementation examples
- ✅ File Upload Manager component
- ✅ Drag and drop upload examples
- ✅ Image preview and cropping examples
- Multiple Upload Methods - Direct file upload or upload from URL
- File Validation - Size limits (15MB) and type validation
- Progress Tracking - Upload progress indicators
- Google Cloud Storage - Integration with GCS for file storage
- Security - Authentication, permissions, and bucket validation
- Client-side examples - Ready-to-use JavaScript code
All endpoints are fully documented with request/response examples, client-server communication flows, and implementation guidance.
Last Updated: January 2025