0.6.4-thumb-upgrade
This commit is contained in:
473
docs/upload-client-guide.md
Normal file
473
docs/upload-client-guide.md
Normal file
@@ -0,0 +1,473 @@
|
||||
# 📤 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`:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
### Формат токена
|
||||
- JWT токен с claims: `{ user_id, username, exp?, iat? }`
|
||||
- Минимальная длина: 100 символов
|
||||
- Максимальная длина: 2048 символов
|
||||
|
||||
## 📊 Информация о пользователе
|
||||
|
||||
### GET /
|
||||
|
||||
Получает информацию о текущем пользователе и его квоте.
|
||||
|
||||
**Заголовки:**
|
||||
```http
|
||||
Authorization: Bearer <jwt-token>
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"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 /
|
||||
|
||||
Загружает один или несколько файлов.
|
||||
|
||||
**Заголовки:**
|
||||
```http
|
||||
Authorization: Bearer <jwt-token>
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
**Параметры:**
|
||||
- `file` (multipart) - Файл для загрузки (можно несколько)
|
||||
|
||||
**Лимиты:**
|
||||
- Максимальный размер одного файла: **500 МБ**
|
||||
- Максимальная квота пользователя: **12 ГБ**
|
||||
- Поддерживаемые форматы: изображения, аудио, видео, документы
|
||||
|
||||
**Ответ (один файл):**
|
||||
```
|
||||
filename-uuid.ext
|
||||
```
|
||||
|
||||
**Ответ (несколько файлов):**
|
||||
```json
|
||||
{
|
||||
"uploaded_files": [
|
||||
"file1-uuid.ext",
|
||||
"file2-uuid.ext"
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
```
|
||||
|
||||
**Коды ответов:**
|
||||
- `200` - Файл(ы) успешно загружены
|
||||
- `400` - Нет файлов или все файлы пустые
|
||||
- `401` - Неверный токен авторизации
|
||||
- `413` - Превышен лимит размера файла или квоты
|
||||
- `415` - Неподдерживаемый формат файла
|
||||
- `500` - Ошибка сервера
|
||||
|
||||
## 📁 Получение файлов
|
||||
|
||||
### GET /{filename}
|
||||
|
||||
Получает загруженный файл.
|
||||
|
||||
**Параметры:**
|
||||
- `filename` - Имя файла (UUID + расширение)
|
||||
|
||||
**Заголовки (опционально):**
|
||||
```http
|
||||
If-None-Match: "etag-value"
|
||||
Range: bytes=0-1023
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
- `200` - Файл найден
|
||||
- `304` - Файл не изменился (если передан ETag)
|
||||
- `404` - Файл не найден
|
||||
- `206` - Частичный контент (если передан Range)
|
||||
|
||||
## 💻 Примеры кода
|
||||
|
||||
### JavaScript/TypeScript
|
||||
|
||||
```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
|
||||
|
||||
```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
|
||||
|
||||
```bash
|
||||
# Получение информации о пользователе
|
||||
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` | Ошибка сервера | Повторить запрос позже |
|
||||
|
||||
### Пример обработки ошибок
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```bash
|
||||
# Обязательные
|
||||
JWT_SECRET=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
|
||||
```
|
||||
|
||||
### Лимиты
|
||||
|
||||
```rust
|
||||
// Максимальный размер одного файла
|
||||
const MAX_SINGLE_FILE_BYTES: u64 = 500 * 1024 * 1024; // 500 МБ
|
||||
|
||||
// Максимальная квота пользователя
|
||||
const MAX_USER_QUOTA_BYTES: u64 = 12 * 1024 * 1024 * 1024; // 12 ГБ
|
||||
```
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
### Проверка подключения
|
||||
|
||||
```bash
|
||||
# Проверка доступности API
|
||||
curl -I https://files.dscrs.site/
|
||||
|
||||
# Проверка аутентификации
|
||||
curl -H "Authorization: Bearer your-token" \
|
||||
https://files.dscrs.site/
|
||||
```
|
||||
|
||||
### Тестовые файлы
|
||||
|
||||
```bash
|
||||
# Создание тестового файла
|
||||
echo "Test content" > test.txt
|
||||
|
||||
# Загрузка тестового файла
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer your-token" \
|
||||
-F "file=@test.txt" \
|
||||
https://files.dscrs.site/
|
||||
```
|
||||
|
||||
## 📚 Дополнительные ресурсы
|
||||
|
||||
- [API Reference](README.md)
|
||||
- [Setup Guide](SETUP.md)
|
||||
- [Features Overview](features.md)
|
||||
- [CORS Configuration](architecture.md)
|
||||
|
||||
## 🆘 Поддержка
|
||||
|
||||
При возникновении проблем:
|
||||
|
||||
1. Проверьте валидность JWT токена
|
||||
2. Убедитесь в правильности Content-Type
|
||||
3. Проверьте размер файла и квоту пользователя
|
||||
4. Изучите логи сервера для детальной диагностики
|
||||
|
||||
---
|
||||
|
||||
**💡 Совет**: Используйте streaming загрузку для больших файлов и всегда проверяйте квоту пользователя перед загрузкой.
|
||||
Reference in New Issue
Block a user