rest-api-docs.md 19 KB

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:

    {
    "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:

    {
    "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:

    {
    "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:

    {
    "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:

    {
    "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:

    [
    {
      "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
/**
 * Example of uploading a file using chunked upload
 */
function uploadSharedFile($file_path, $description, $password, $reference = '') {
    $api_key = 'your-api-key-here';
    $api_url = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
    
    // File information
    $file_name = basename($file_path);
    $file_size = filesize($file_path);
    $chunk_size = 1024 * 1024; // 1MB chunks
    $total_chunks = ceil($file_size / $chunk_size);
    
    // Step 1: Initialize upload
    $ch = curl_init($api_url . '/upload/init');
    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'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'original_file_name' => $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

/**
 * 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
/**
 * Example of retrieving a list of files
 */
function getSharedFiles($page = 1, $perPage = 10, $orderBy = 'upload_date', $order = 'DESC', $fileName = '', $reference = '') {
    $api_key = 'your-api-key-here';
    $api_url = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
    
    // Build query parameters
    $query_params = http_build_query([
        'page' => $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

/**
 * 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:

{
  "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