Files
quoter/docs/upload-client-guide.md
Untone 5baba346e0
Some checks failed
Deploy quoter Microservice on push / deploy (push) Failing after 39m16s
;### Changed
- 🔑 **JWT_SECRET → JWT_SECRET_KEY**: Используется `JWT_SECRET_KEY` для совместимости с `@core`, `@inbox`, `@presence`
  - Fallback на `JWT_SECRET` для обратной совместимости
  - Обновлена документация: README.md, configuration.md
  - **BREAKING**: Требует установки `JWT_SECRET_KEY` в production (или использование legacy `JWT_SECRET`)

### Fixed (Tests & Code Quality)
- 🧪 **Удален мертвый код**: Removed unused mock functions and structs from tests
- 🔧 **Исправлены async тесты**: Changed `#[test]` → `#[tokio::test]` для async функций
- 🧹 **Чистые warnings**: Все тесты компилируются без warnings
- 📝 **Префиксы unused полей**: `_field` вместо `#[allow(dead_code)]`
2025-09-30 21:46:47 +03:00

13 KiB
Raw Permalink Blame History

📤 Upload Client Guide - Quoter API

Версия: 0.6.2
Дата: 2025-01-28
Статус: 🚀 Production Ready

🎯 Обзор

Quoter предоставляет простой и надежный API для загрузки файлов с поддержкой:

  • JWT аутентификации
  • Квот пользователей (12 ГБ на пользователя)
  • Множественных файлов в одном запросе
  • Автоматического определения MIME типов
  • Streaming загрузки с проверкой квот

🔗 Endpoints

Base URL

Production: https://files.dscrs.site
Development: http://localhost:8080

API Endpoints

Method Endpoint Описание
GET / Информация о пользователе и квоте
POST / Загрузка файлов
GET /{filename} Получение файла

🔐 Аутентификация

POST запросы требуют JWT токен в заголовке Authorization:

Authorization: Bearer <your-jwt-token>

Формат токена

  • JWT токен с claims: { user_id, username, exp?, iat? }
  • Минимальная длина: 100 символов
  • Максимальная длина: 2048 символов

📊 Информация о пользователе

GET /

Получает информацию о текущем пользователе и его квоте.

Заголовки:

Authorization: Bearer <jwt-token>

Ответ:

{
  "user_id": "user123",
  "username": "john_doe",
  "token_type": "Bearer",
  "created_at": "2025-01-28T10:00:00Z",
  "last_activity": "2025-01-28T12:00:00Z",
  "auth_data": {...},
  "device_info": {...},
  "quota": {
    "current_quota": 1073741824,
    "max_quota": 12884901888,
    "usage_percentage": 8.33
  }
}

Коды ответов:

  • 200 - Успешно
  • 401 - Неверный или истекший токен

📤 Загрузка файлов

POST /

Загружает один или несколько файлов.

Заголовки:

Authorization: Bearer <jwt-token>
Content-Type: multipart/form-data

Параметры:

  • file (multipart) - Файл для загрузки (можно несколько)

Лимиты:

  • Максимальный размер одного файла: 500 МБ
  • Максимальная квота пользователя: 12 ГБ
  • Поддерживаемые форматы: изображения, аудио, видео, документы

Ответ (один файл):

filename-uuid.ext

Ответ (несколько файлов):

{
  "uploaded_files": [
    "file1-uuid.ext",
    "file2-uuid.ext"
  ],
  "count": 2
}

Коды ответов:

  • 200 - Файл(ы) успешно загружены
  • 400 - Нет файлов или все файлы пустые
  • 401 - Неверный токен авторизации
  • 413 - Превышен лимит размера файла или квоты
  • 415 - Неподдерживаемый формат файла
  • 500 - Ошибка сервера

📁 Получение файлов

GET /{filename}

Получает загруженный файл.

Параметры:

  • filename - Имя файла (UUID + расширение)

Заголовки (опционально):

If-None-Match: "etag-value"
Range: bytes=0-1023

Ответ:

  • 200 - Файл найден
  • 304 - Файл не изменился (если передан ETag)
  • 404 - Файл не найден
  • 206 - Частичный контент (если передан Range)

💻 Примеры кода

JavaScript/TypeScript

class QuoterUploadClient {
  private baseUrl: string;
  private token: string;

  constructor(baseUrl: string, token: string) {
    this.baseUrl = baseUrl;
    this.token = token;
  }

  // Получение информации о пользователе
  async getUserInfo() {
    const response = await fetch(`${this.baseUrl}/`, {
      headers: {
        'Authorization': `Bearer ${this.token}`
      }
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    return await response.json();
  }

  // Загрузка одного файла
  async uploadFile(file: File): Promise<string> {
    const formData = new FormData();
    formData.append('file', file);

    const response = await fetch(`${this.baseUrl}/`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.token}`
      },
      body: formData
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Upload failed: ${response.status} ${errorText}`);
    }

    return await response.text();
  }

  // Загрузка нескольких файлов
  async uploadFiles(files: File[]): Promise<{uploaded_files: string[], count: number}> {
    const formData = new FormData();
    files.forEach(file => formData.append('file', file));

    const response = await fetch(`${this.baseUrl}/`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.token}`
      },
      body: formData
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Upload failed: ${response.status} ${errorText}`);
    }

    return await response.json();
  }

  // Получение файла
  async getFile(filename: string): Promise<Blob> {
    const response = await fetch(`${this.baseUrl}/${filename}`, {
      headers: {
        'Authorization': `Bearer ${this.token}`
      }
    });

    if (!response.ok) {
      throw new Error(`File not found: ${response.status}`);
    }

    return await response.blob();
  }
}

// Использование
const client = new QuoterUploadClient('https://files.dscrs.site', 'your-jwt-token');

// Получение информации о пользователе
const userInfo = await client.getUserInfo();
console.log(`Квота: ${userInfo.quota.usage_percentage.toFixed(1)}%`);

// Загрузка файла
const fileInput = document.getElementById('fileInput') as HTMLInputElement;
const file = fileInput.files[0];
const filename = await client.uploadFile(file);
console.log(`Файл загружен: ${filename}`);

Python

import requests
import json
from typing import List, Dict, Any

class QuoterUploadClient:
    def __init__(self, base_url: str, token: str):
        self.base_url = base_url.rstrip('/')
        self.token = token
        self.headers = {'Authorization': f'Bearer {token}'}

    def get_user_info(self) -> Dict[str, Any]:
        """Получение информации о пользователе"""
        response = requests.get(f'{self.base_url}/', headers=self.headers)
        response.raise_for_status()
        return response.json()

    def upload_file(self, file_path: str) -> str:
        """Загрузка одного файла"""
        with open(file_path, 'rb') as f:
            files = {'file': f}
            response = requests.post(
                f'{self.base_url}/', 
                headers=self.headers, 
                files=files
            )
            response.raise_for_status()
            return response.text.strip()

    def upload_files(self, file_paths: List[str]) -> Dict[str, Any]:
        """Загрузка нескольких файлов"""
        files = []
        for path in file_paths:
            files.append(('file', open(path, 'rb')))
        
        try:
            response = requests.post(
                f'{self.base_url}/', 
                headers=self.headers, 
                files=files
            )
            response.raise_for_status()
            return response.json()
        finally:
            # Закрываем все файлы
            for _, file_obj in files:
                file_obj.close()

    def get_file(self, filename: str, save_path: str = None) -> bytes:
        """Получение файла"""
        response = requests.get(
            f'{self.base_url}/{filename}', 
            headers=self.headers
        )
        response.raise_for_status()
        
        if save_path:
            with open(save_path, 'wb') as f:
                f.write(response.content)
        
        return response.content

# Использование
client = QuoterUploadClient('https://files.dscrs.site', 'your-jwt-token')

# Получение информации о пользователе
user_info = client.get_user_info()
print(f"Квота: {user_info['quota']['usage_percentage']:.1f}%")

# Загрузка файла
filename = client.upload_file('/path/to/file.jpg')
print(f"Файл загружен: {filename}")

# Получение файла
file_content = client.get_file(filename, '/path/to/downloaded_file.jpg')

cURL

# Получение информации о пользователе
curl -H "Authorization: Bearer your-jwt-token" \
     https://files.dscrs.site/

# Загрузка одного файла
curl -X POST \
     -H "Authorization: Bearer your-jwt-token" \
     -F "file=@/path/to/file.jpg" \
     https://files.dscrs.site/

# Загрузка нескольких файлов
curl -X POST \
     -H "Authorization: Bearer your-jwt-token" \
     -F "file=@/path/to/file1.jpg" \
     -F "file=@/path/to/file2.mp3" \
     https://files.dscrs.site/

# Получение файла
curl -H "Authorization: Bearer your-jwt-token" \
     -o downloaded_file.jpg \
     https://files.dscrs.site/filename-uuid.jpg

🚨 Обработка ошибок

Типичные ошибки

Код Описание Решение
401 Неверный токен Проверить валидность JWT токена
413 Превышена квота Удалить старые файлы или увеличить квоту
413 Файл слишком большой Разделить файл на части
415 Неподдерживаемый формат Проверить MIME тип файла
500 Ошибка сервера Повторить запрос позже

Пример обработки ошибок

async function uploadWithRetry(client: QuoterUploadClient, file: File, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await client.uploadFile(file);
    } catch (error) {
      if (error.message.includes('413')) {
        throw new Error('Файл слишком большой или квота превышена');
      }
      
      if (error.message.includes('401')) {
        throw new Error('Токен авторизации недействителен');
      }
      
      if (attempt === maxRetries) {
        throw error;
      }
      
      // Ждем перед повторной попыткой
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

📋 Поддерживаемые форматы

Изображения

  • JPEG, PNG, GIF, WebP, HEIC, BMP, TIFF

Аудио

  • MP3, WAV, AAC, M4A, OGG, FLAC

Видео

  • MP4, AVI, MOV, WebM, MKV

Документы

  • PDF, DOC, DOCX, TXT, RTF

🔧 Конфигурация

Environment Variables

# Обязательные
JWT_SECRET_KEY=your-jwt-secret-key
REDIS_URL=redis://localhost:6379
STORJ_ACCESS_KEY=your-storj-access-key
STORJ_SECRET_KEY=your-storj-secret-key
STORJ_BUCKET=your-bucket-name

# Опциональные
PORT=8080
CORS_DOWNLOAD_ORIGINS=https://discours.io,https://*.discours.io

Лимиты

// Максимальный размер одного файла
const MAX_SINGLE_FILE_BYTES: u64 = 500 * 1024 * 1024; // 500 МБ

// Максимальная квота пользователя
const MAX_USER_QUOTA_BYTES: u64 = 12 * 1024 * 1024 * 1024; // 12 ГБ

🧪 Тестирование

Проверка подключения

# Проверка доступности API
curl -I https://files.dscrs.site/

# Проверка аутентификации
curl -H "Authorization: Bearer your-token" \
     https://files.dscrs.site/

Тестовые файлы

# Создание тестового файла
echo "Test content" > test.txt

# Загрузка тестового файла
curl -X POST \
     -H "Authorization: Bearer your-token" \
     -F "file=@test.txt" \
     https://files.dscrs.site/

📚 Дополнительные ресурсы

🆘 Поддержка

При возникновении проблем:

  1. Проверьте валидность JWT токена
  2. Убедитесь в правильности Content-Type
  3. Проверьте размер файла и квоту пользователя
  4. Изучите логи сервера для детальной диагностики

💡 Совет: Используйте streaming загрузку для больших файлов и всегда проверяйте квоту пользователя перед загрузкой.