# QDR Temporary Shared Storage - REST API Documentation ## Introduction The QDR Temporary Shared Storage plugin provides a comprehensive REST API for uploading, managing, and downloading temporary shared files. This document outlines the available endpoints, authentication requirements, and provides examples in both PHP and JavaScript. ## Authentication All API requests require an API key sent in the `X-Api-Key` HTTP header. You can generate or view your API key in the plugin settings page at "Settings > Shared Storage". ## API Base URL The base URL for all API endpoints is: ``` https://your-wordpress-site.com/wp-json/qdr-tss/v1/ ``` ## Endpoints ### 1. Initialize Chunked Upload Prepares the server for a chunked upload. - **Endpoint**: `/upload/init` - **Method**: `POST` - **Parameters**: - `original_file_name`: The original name of the file being uploaded - `file_size`: The total size of the file in bytes - **Response**: ```json { "upload_id": "5f8d7a6b9c3e", "status": "initialized" } ``` ### 2. Upload Chunk Uploads a single chunk of the file. - **Endpoint**: `/upload/chunk` - **Method**: `POST` - **Parameters**: - `upload_id`: The upload ID received from the initialization - `chunk_index`: The index of the current chunk (starting from 0) - `total_chunks`: The total number of chunks - `chunk`: The chunk file data (multipart/form-data) - **Response**: ```json { "upload_id": "5f8d7a6b9c3e", "chunk_index": 0, "status": "chunk_uploaded" } ``` ### 3. Finalize Upload Combines all chunks and creates the file record. - **Endpoint**: `/upload/finalize` - **Method**: `POST` - **Parameters**: - `upload_id`: The upload ID received from the initialization - `total_chunks`: The total number of chunks - `original_file_name`: The original name of the file - `description`: Description of the file - `message`: Custom message (optional) - `active_from`: Date from which permalink will be active (optional, defaults to current time) - `active_to`: Date until which permalink will be active (optional, defaults to 30 days from now) - `password`: Password for download protection - `reference`: Custom reference value (optional) - **Response**: ```json { "fileid": "1621234567abcdef890", "permalink": "https://your-wordpress-site.com/shared-file-download/1621234567abcdef890", "shortcode": "[qdr_tss_download id=\"1621234567abcdef890\"]" } ``` ### 4. Get File Details Retrieves details about a specific file. - **Endpoint**: `/media/{fileid}` - **Method**: `GET` - **Response**: ```json { "id": 1, "fileid": "1621234567abcdef890", "original_file_name": "example.pdf", "description": "Example document", "message": "Please review this document", "active_from": "2025-05-19 15:30:00", "active_to": "2025-06-18 15:30:00", "reference": "REF123", "upload_date": "2025-05-19 15:30:00", "download_count": 5, "last_download": "2025-05-20 10:45:30", "file_size": 1048576 } ``` ### 5. Update File Updates properties of a specific file. - **Endpoint**: `/media/{fileid}` - **Method**: `PUT` - **Parameters**: - `message`: Custom message (optional) - `active_from`: Date from which permalink will be active (optional) - `active_to`: Date until which permalink will be active (optional) - `reference`: Custom reference value (optional) - `description`: File description (optional) - **Response**: Updated file details (same format as Get File Details) ### 6. Delete File Deletes a specific file. - **Endpoint**: `/media/{fileid}` - **Method**: `DELETE` - **Response**: ```json { "deleted": true, "fileid": "1621234567abcdef890" } ``` ### 7. List Files Retrieves a list of files with pagination and filtering. - **Endpoint**: `/media` - **Method**: `GET` - **Parameters**: - `page`: Page number (optional, default: 1) - `per_page`: Items per page (optional, default: 10) - `orderby`: Field to sort by (optional, default: "upload_date") - `order`: Sort order (optional, default: "DESC") - `original_file_name`: Filter by file name (optional) - `reference`: Filter by reference (optional) - **Response**: ```json [ { "id": 1, "fileid": "1621234567abcdef890", "original_file_name": "example.pdf", "description": "Example document", "message": "Please review this document", "active_from": "2025-05-19 15:30:00", "active_to": "2025-06-18 15:30:00", "reference": "REF123", "upload_date": "2025-05-19 15:30:00", "download_count": 5, "last_download": "2025-05-20 10:45:30", "file_size": 1048576 } ] ``` ## Code Examples ### PHP Example: Complete File Upload ```php $file_name, 'file_size' => $file_size ])); $response = curl_exec($ch); curl_close($ch); $init_result = json_decode($response, true); if (!isset($init_result['upload_id'])) { throw new Exception('Failed to initialize upload: ' . $response); } $upload_id = $init_result['upload_id']; // Step 2: Upload chunks $file = fopen($file_path, 'rb'); for ($i = 0; $i < $total_chunks; $i++) { // Create a temporary file for the chunk $chunk_file = tempnam(sys_get_temp_dir(), 'chunk'); $chunk_handle = fopen($chunk_file, 'wb'); // Read chunk from original file $chunk_data = fread($file, $chunk_size); fwrite($chunk_handle, $chunk_data); fclose($chunk_handle); // Upload the chunk $ch = curl_init($api_url . '/upload/chunk'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'X-Api-Key: ' . $api_key ]); $post_data = [ 'upload_id' => $upload_id, 'chunk_index' => $i, 'total_chunks' => $total_chunks, 'chunk' => new CURLFile($chunk_file, 'application/octet-stream', 'chunk') ]; curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); $response = curl_exec($ch); curl_close($ch); // Delete temporary chunk file unlink($chunk_file); $chunk_result = json_decode($response, true); if (!isset($chunk_result['status']) || $chunk_result['status'] !== 'chunk_uploaded') { throw new Exception('Failed to upload chunk ' . $i . ': ' . $response); } // Display progress echo "Uploaded chunk " . ($i + 1) . " of " . $total_chunks . "\n"; } fclose($file); // Step 3: Finalize upload $ch = curl_init($api_url . '/upload/finalize'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'X-Api-Key: ' . $api_key, 'Content-Type: application/json' ]); // Set expiration to 30 days from now $active_to = date('Y-m-d H:i:s', strtotime('+30 days')); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'upload_id' => $upload_id, 'total_chunks' => $total_chunks, 'original_file_name' => $file_name, 'description' => $description, 'message' => 'Uploaded via API example', 'password' => $password, 'reference' => $reference, 'active_to' => $active_to ])); $response = curl_exec($ch); curl_close($ch); $final_result = json_decode($response, true); if (!isset($final_result['fileid'])) { throw new Exception('Failed to finalize upload: ' . $response); } return $final_result; } // Example usage try { $result = uploadSharedFile( '/path/to/your/file.pdf', 'Example document uploaded via API', 'secure_password', 'REF123' ); echo "File uploaded successfully!\n"; echo "File ID: " . $result['fileid'] . "\n"; echo "Permalink: " . $result['permalink'] . "\n"; echo "Shortcode: " . $result['shortcode'] . "\n"; } catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; } ``` ### JavaScript Example: Chunked File Upload ```javascript /** * Uploads a file using chunked upload * @param {File} file The file to upload * @param {string} description File description * @param {string} password Password for download protection * @param {string} reference Optional reference * @param {function} progressCallback Callback for upload progress * @returns {Promise} Promise resolving to the upload result */ async function uploadSharedFile(file, description, password, reference = '', progressCallback = null) { const apiKey = 'your-api-key-here'; const apiUrl = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1'; const chunkSize = 1024 * 1024; // 1MB chunks const totalChunks = Math.ceil(file.size / chunkSize); // Step 1: Initialize upload const initResponse = await fetch(`${apiUrl}/upload/init`, { method: 'POST', headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ original_file_name: file.name, file_size: file.size }) }); const initResult = await initResponse.json(); if (!initResult.upload_id) { throw new Error('Failed to initialize upload'); } const uploadId = initResult.upload_id; // Step 2: Upload chunks for (let i = 0; i < totalChunks; i++) { const start = i * chunkSize; const end = Math.min(file.size, start + chunkSize); const chunk = file.slice(start, end); const formData = new FormData(); formData.append('upload_id', uploadId); formData.append('chunk_index', i); formData.append('total_chunks', totalChunks); formData.append('chunk', chunk); const chunkResponse = await fetch(`${apiUrl}/upload/chunk`, { method: 'POST', headers: { 'X-Api-Key': apiKey }, body: formData }); const chunkResult = await chunkResponse.json(); if (!chunkResult.status || chunkResult.status !== 'chunk_uploaded') { throw new Error(`Failed to upload chunk ${i}`); } // Update progress if (progressCallback) { progressCallback((i + 1) / totalChunks * 100); } } // Step 3: Finalize upload const activeToDate = new Date(); activeToDate.setDate(activeToDate.getDate() + 30); // 30 days from now const activeTo = activeToDate.toISOString().slice(0, 19).replace('T', ' '); const finalizeResponse = await fetch(`${apiUrl}/upload/finalize`, { method: 'POST', headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ upload_id: uploadId, total_chunks: totalChunks, original_file_name: file.name, description: description, message: 'Uploaded via JavaScript API example', password: password, reference: reference, active_to: activeTo }) }); const finalResult = await finalizeResponse.json(); if (!finalResult.fileid) { throw new Error('Failed to finalize upload'); } return finalResult; } // Example usage with HTML file input document.getElementById('file-input').addEventListener('change', async (event) => { const file = event.target.files[0]; if (!file) return; const progressBar = document.getElementById('progress-bar'); try { const result = await uploadSharedFile( file, 'Example document uploaded via JavaScript', 'secure_password', 'REF123', (progress) => { progressBar.style.width = `${progress}%`; } ); console.log('File uploaded successfully!'); console.log(`File ID: ${result.fileid}`); console.log(`Permalink: ${result.permalink}`); console.log(`Shortcode: ${result.shortcode}`); } catch (error) { console.error('Error:', error.message); } }); ``` ### PHP Example: Get File List ```php $page, 'per_page' => $perPage, 'orderby' => $orderBy, 'order' => $order, 'original_file_name' => $fileName, 'reference' => $reference ]); $ch = curl_init($api_url . '/media?' . $query_params); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'X-Api-Key: ' . $api_key ]); $response = curl_exec($ch); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $header = substr($response, 0, $header_size); $body = substr($response, $header_size); // Get pagination headers $total = 0; $total_pages = 0; foreach (explode("\r\n", $header) as $line) { if (strpos($line, 'X-WP-Total:') === 0) { $total = intval(trim(str_replace('X-WP-Total:', '', $line))); } else if (strpos($line, 'X-WP-TotalPages:') === 0) { $total_pages = intval(trim(str_replace('X-WP-TotalPages:', '', $line))); } } curl_close($ch); $files = json_decode($body, true); return [ 'files' => $files, 'total' => $total, 'total_pages' => $total_pages ]; } // Example usage $result = getSharedFiles(1, 10, 'upload_date', 'DESC', '', 'REF123'); echo "Total files: " . $result['total'] . "\n"; echo "Total pages: " . $result['total_pages'] . "\n"; foreach ($result['files'] as $file) { echo "ID: " . $file['fileid'] . ", Name: " . $file['original_file_name'] . "\n"; } ``` ### JavaScript Example: Update File Properties ```javascript /** * Updates file properties * @param {string} fileid The file ID * @param {object} properties Properties to update * @returns {Promise} Promise resolving to the updated file */ async function updateSharedFile(fileid, properties) { const apiKey = 'your-api-key-here'; const apiUrl = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1'; const response = await fetch(`${apiUrl}/media/${fileid}`, { method: 'PUT', headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(properties) }); const result = await response.json(); if (!result.fileid) { throw new Error('Failed to update file'); } return result; } // Example usage async function exampleUpdateFile() { try { const updatedFile = await updateSharedFile('1621234567abcdef890', { message: 'Updated message', active_to: '2025-07-19 15:30:00', reference: 'UPDATED-REF' }); console.log('File updated successfully!'); console.log(updatedFile); } catch (error) { console.error('Error:', error.message); } } exampleUpdateFile(); ``` ## Error Handling All API endpoints return standard HTTP status codes: - `200 OK`: Request succeeded - `201 Created`: Resource created successfully - `400 Bad Request`: Invalid request parameters - `401 Unauthorized`: Missing API key - `403 Forbidden`: Invalid API key - `404 Not Found`: Resource not found - `500 Internal Server Error`: Server error Error responses include a message explaining the error: ```json { "code": "qdr_tss_not_found", "message": "File not found.", "data": { "status": 404 } } ``` ## Important Changes from Previous Version ### Key Updates 1. **File ID Format**: Changed from simple `media_id` to `fileid` with a more complex alphanumeric format for better uniqueness 2. **Response Format**: All endpoints now return `fileid` instead of `media_id` 3. **Security**: Enhanced API key validation and better error messages 4. **File Storage**: Improved file path generation with date-based directory structure 5. **Chunked Uploads**: More robust chunk handling with better error recovery ### Breaking Changes - **API Response Field**: `media_id` has been replaced with `fileid` in all responses - **URL Parameters**: Download URLs now use the new `fileid` format - **Database Schema**: Internal changes to support the new file ID system ### New Features - **Force Activate**: Admin interface includes new functionality to reset active periods - **Enhanced Error Handling**: More descriptive error messages for debugging - **Better File Management**: Improved cleanup and storage management - **Responsive UI**: Better mobile support for admin interface ## Rate Limiting There are currently no rate limits on the API, but excessive usage may be throttled in the future. Consider implementing your own rate limiting on the client side for production applications. ## Best Practices 1. **Always use chunked uploads** for files larger than 1MB to avoid timeout issues 2. **Set reasonable expiration dates** to prevent storage bloat 3. **Use unique reference codes** to help organize and identify files 4. **Implement proper error handling** in your client applications 5. **Validate file types and sizes** before upload to provide better user experience 6. **Store file IDs securely** in your application database for future reference 7. **Monitor storage usage** and implement cleanup procedures 8. **Use HTTPS** for all API communications to protect sensitive data ## Storage Management The plugin includes built-in storage management features: - **Storage Limits**: Configure maximum total storage size in plugin settings - **Automatic Cleanup**: Hourly cron job removes expired files - **Manual Purge**: Admin interface button for immediate cleanup - **Storage Tracking**: Real-time storage usage monitoring ## Security Features - **API Key Authentication**: All endpoints require valid API key - **Password Protection**: Downloaded files require password verification - **Reference Codes**: Optional additional security layer - **Time-based Access**: Files only accessible during active period - **Protected Storage**: Files stored outside web-accessible directory - **Download Tracking**: Audit trail of all file access