Files
AuthCentral/AVATAR_UPLOAD_API.md
Beyhan Oğur 8b1fbdee99 first commit
2026-04-26 21:37:58 +03:00

639 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📤 Avatar Upload API - Multipart Form Data
## ✨ Özellikler
Avatar artık **multipart/form-data** ile dosya upload olarak gönderilir (JSON değil).
### ✅ Desteklenen Özellikler
- 📁 Dosya upload (multipart/form-data)
- 🖼️ Format kontrolü (jpg, jpeg, png, gif, webp)
- 📏 Boyut kontrolü (max 5MB)
- 🗑️ Eski avatar otomatik silme
- 👤 Kullanıcı kendi avatar'ını yükleyebilir
- 👨‍💼 Admin herhangi bir kullanıcının avatar'ını yükleyebilir
- 🌐 Static file serving
---
## 📋 Endpoint'ler
### 1. Kullanıcı Kendi Avatar'ını Yükler
```
POST /v1/user/avatar
Content-Type: multipart/form-data
Authorization: Bearer {token}
```
**Form Data:**
- `avatar` (file, required) - Avatar dosyası
**Desteklenen Formatlar:**
- JPG / JPEG
- PNG
- GIF
- WebP
**Maksimum Boyut:** 5MB
**Response (200):**
```json
{
"message": "Avatar uploaded successfully",
"avatar_url": "/uploads/avatars/user-uuid_1234567890.jpg",
"user": {
"id": "uuid",
"username": "john_doe",
"email": "john@example.com",
"avatar": "/uploads/avatars/user-uuid_1234567890.jpg"
}
}
```
**cURL Örneği:**
```bash
curl -X POST http://localhost:8080/v1/user/avatar \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "avatar=@/path/to/image.jpg"
```
---
### 2. Kullanıcı Avatar'ını Siler
```
DELETE /v1/user/avatar
Authorization: Bearer {token}
```
**Response (200):**
```json
{
"message": "Avatar deleted successfully",
"user": {
"id": "uuid",
"username": "john_doe",
"email": "john@example.com",
"avatar": ""
}
}
```
**cURL Örneği:**
```bash
curl -X DELETE http://localhost:8080/v1/user/avatar \
-H "Authorization: Bearer YOUR_TOKEN"
```
---
### 3. Admin Kullanıcı Avatar'ı Yükler
```
POST /v1/admin/users/{user_id}/avatar
Content-Type: multipart/form-data
Authorization: Bearer {admin_token}
```
**Form Data:**
- `avatar` (file, required) - Avatar dosyası
**Response (200):**
```json
{
"message": "Avatar uploaded successfully",
"avatar_url": "/uploads/avatars/user-uuid_1234567890.jpg",
"user": {
"id": "uuid",
"username": "john_doe",
"email": "john@example.com",
"avatar": "/uploads/avatars/user-uuid_1234567890.jpg"
}
}
```
**cURL Örneği:**
```bash
curl -X POST http://localhost:8080/v1/admin/users/USER_ID/avatar \
-H "Authorization: Bearer ADMIN_TOKEN" \
-F "avatar=@/path/to/image.jpg"
```
---
### 4. Avatar Görüntüleme (Static File)
```
GET /uploads/avatars/{filename}
```
Avatar dosyaları otomatik olarak static file server tarafından sunulur.
**Örnek:**
```
http://localhost:8080/uploads/avatars/user-uuid_1234567890.jpg
```
**HTML'de Kullanım:**
```html
<img src="http://localhost:8080/uploads/avatars/user-uuid_1234567890.jpg" alt="Avatar">
```
---
## 🧪 Test Örnekleri
### Test 1: Avatar Yükleme (cURL)
```bash
# 1. Login olun
curl -X POST http://localhost:8080/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "Test123!"
}'
# Response'dan token alın
TOKEN="eyJhbGci..."
# 2. Avatar yükleyin
curl -X POST http://localhost:8080/v1/user/avatar \
-H "Authorization: Bearer $TOKEN" \
-F "avatar=@./my-photo.jpg"
# Response:
# {
# "message": "Avatar uploaded successfully",
# "avatar_url": "/uploads/avatars/uuid_1234567890.jpg"
# }
```
### Test 2: Avatar Silme
```bash
curl -X DELETE http://localhost:8080/v1/user/avatar \
-H "Authorization: Bearer $TOKEN"
```
### Test 3: Admin Avatar Upload
```bash
# Admin token ile
ADMIN_TOKEN="admin_token_here"
USER_ID="user-uuid-here"
curl -X POST http://localhost:8080/v1/admin/users/$USER_ID/avatar \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-F "avatar=@./profile-picture.jpg"
```
---
## 💻 Frontend Kullanımı
### HTML Form
```html
<form id="avatarForm">
<input type="file" id="avatarInput" accept="image/*" required>
<button type="submit">Upload Avatar</button>
</form>
<script>
document.getElementById('avatarForm').addEventListener('submit', async (e) => {
e.preventDefault();
const fileInput = document.getElementById('avatarInput');
const file = fileInput.files[0];
if (!file) {
alert('Please select a file');
return;
}
const formData = new FormData();
formData.append('avatar', file);
const token = localStorage.getItem('access_token');
try {
const response = await fetch('http://localhost:8080/v1/user/avatar', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
const data = await response.json();
if (response.ok) {
console.log('Avatar uploaded:', data.avatar_url);
alert('Avatar uploaded successfully!');
// Avatar'ı göster
document.getElementById('userAvatar').src =
'http://localhost:8080' + data.avatar_url;
} else {
alert('Error: ' + data.error);
}
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed');
}
});
</script>
```
### React Component
```jsx
import { useState } from 'react';
function AvatarUpload() {
const [uploading, setUploading] = useState(false);
const [avatarUrl, setAvatarUrl] = useState('');
const handleUpload = async (e) => {
e.preventDefault();
const file = e.target.avatar.files[0];
if (!file) return;
// Validate file size
if (file.size > 5 * 1024 * 1024) {
alert('File size must be less than 5MB');
return;
}
// Validate file type
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
alert('Only JPG, PNG, GIF, and WebP images are allowed');
return;
}
const formData = new FormData();
formData.append('avatar', file);
const token = localStorage.getItem('access_token');
setUploading(true);
try {
const response = await fetch('http://localhost:8080/v1/user/avatar', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
const data = await response.json();
if (response.ok) {
setAvatarUrl('http://localhost:8080' + data.avatar_url);
alert('Avatar uploaded successfully!');
} else {
alert('Error: ' + data.error);
}
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed');
} finally {
setUploading(false);
}
};
const handleDelete = async () => {
const token = localStorage.getItem('access_token');
try {
const response = await fetch('http://localhost:8080/v1/user/avatar', {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
});
const data = await response.json();
if (response.ok) {
setAvatarUrl('');
alert('Avatar deleted successfully!');
} else {
alert('Error: ' + data.error);
}
} catch (error) {
console.error('Delete failed:', error);
}
};
return (
<div className="avatar-upload">
{avatarUrl && (
<div>
<img
src={avatarUrl}
alt="Avatar"
className="w-32 h-32 rounded-full object-cover"
/>
<button onClick={handleDelete} className="btn-danger">
Delete Avatar
</button>
</div>
)}
<form onSubmit={handleUpload}>
<input
type="file"
name="avatar"
accept="image/jpeg,image/jpg,image/png,image/gif,image/webp"
disabled={uploading}
/>
<button type="submit" disabled={uploading}>
{uploading ? 'Uploading...' : 'Upload Avatar'}
</button>
</form>
</div>
);
}
export default AvatarUpload;
```
### Vue.js Component
```vue
<template>
<div class="avatar-upload">
<div v-if="avatarUrl">
<img :src="avatarUrl" alt="Avatar" class="avatar-preview" />
<button @click="deleteAvatar" class="btn-danger">Delete Avatar</button>
</div>
<form @submit.prevent="uploadAvatar">
<input
type="file"
ref="fileInput"
accept="image/*"
@change="handleFileSelect"
:disabled="uploading"
/>
<button type="submit" :disabled="uploading || !selectedFile">
{{ uploading ? 'Uploading...' : 'Upload Avatar' }}
</button>
</form>
<div v-if="error" class="error">{{ error }}</div>
</div>
</template>
<script>
export default {
data() {
return {
avatarUrl: '',
selectedFile: null,
uploading: false,
error: ''
}
},
methods: {
handleFileSelect(event) {
const file = event.target.files[0];
if (!file) return;
// Validate file size (5MB)
if (file.size > 5 * 1024 * 1024) {
this.error = 'File size must be less than 5MB';
this.$refs.fileInput.value = '';
return;
}
// Validate file type
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
this.error = 'Only JPG, PNG, GIF, and WebP images are allowed';
this.$refs.fileInput.value = '';
return;
}
this.selectedFile = file;
this.error = '';
},
async uploadAvatar() {
if (!this.selectedFile) return;
const formData = new FormData();
formData.append('avatar', this.selectedFile);
const token = localStorage.getItem('access_token');
this.uploading = true;
this.error = '';
try {
const response = await fetch('http://localhost:8080/v1/user/avatar', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
const data = await response.json();
if (response.ok) {
this.avatarUrl = 'http://localhost:8080' + data.avatar_url;
this.$refs.fileInput.value = '';
this.selectedFile = null;
} else {
this.error = data.error || 'Upload failed';
}
} catch (error) {
this.error = 'Upload failed: ' + error.message;
} finally {
this.uploading = false;
}
},
async deleteAvatar() {
const token = localStorage.getItem('access_token');
try {
const response = await fetch('http://localhost:8080/v1/user/avatar', {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
this.avatarUrl = '';
} else {
const data = await response.json();
this.error = data.error || 'Delete failed';
}
} catch (error) {
this.error = 'Delete failed: ' + error.message;
}
}
}
}
</script>
<style scoped>
.avatar-preview {
width: 128px;
height: 128px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 1rem;
}
</style>
```
---
## 📊 Dosya Sistemi
### Klasör Yapısı
```
AuthCentral/
├── uploads/
│ └── avatars/
│ ├── uuid1_1234567890.jpg
│ ├── uuid2_1234567891.png
│ └── uuid3_1234567892.webp
├── api/
│ └── handlers/
│ └── avatar_handler.go
└── main.go
```
### Avatar URL Formatı
**Uploaded Files:**
```
/uploads/avatars/{user_id}_{timestamp}.{ext}
Örnek:
/uploads/avatars/550e8400-e29b-41d4-a716-446655440000_1707012345.jpg
```
**OAuth Avatar URLs (değişmez):**
```
https://lh3.googleusercontent.com/a/...
https://avatars.githubusercontent.com/u/...
```
---
## 🔒 Güvenlik
### Dosya Validasyonu
1.**Dosya Boyutu:** Maksimum 5MB
2.**Dosya Formatı:** Sadece jpg, jpeg, png, gif, webp
3.**Authentication:** Bearer token zorunlu
4.**Authorization:** Kullanıcı sadece kendi avatar'ını yükleyebilir (admin hariç)
### Dosya İsimlendirme
- Unique filename: `{user_id}_{timestamp}.{ext}`
- Collision önleme: Timestamp kullanımı
- User ID ile ilişkilendirme
### Eski Dosya Temizleme
- Yeni avatar yüklendiğinde eski dosya otomatik silinir
- Sadece `/uploads/` ile başlayan dosyalar silinir (OAuth URL'leri korunur)
---
## ⚠️ Önemli Notlar
### 1. Static File Serving
Uploads klasörü `/uploads` route'u ile sunuluyor:
```go
r.Static("/uploads", "./uploads")
```
Avatar URL'si:
```
http://localhost:8080/uploads/avatars/filename.jpg
```
### 2. Dosya Boyutu Limiti
Frontend'de validasyon yapmayı unutmayın:
```javascript
if (file.size > 5 * 1024 * 1024) {
alert('File too large! Max 5MB');
return;
}
```
### 3. CORS
Frontend farklı origin'den çalışıyorsa, static files için de CORS gerekebilir.
### 4. Production Deployment
Production'da:
- Cloud storage kullanın (S3, Google Cloud Storage, etc.)
- CDN kullanın
- Image optimization yapın
- Thumbnail oluşturun
---
## 📋 Endpoint Özeti
| Method | Endpoint | Auth | Açıklama |
|--------|----------|------|----------|
| POST | `/v1/user/avatar` | ✅ User | Kendi avatar'ını yükle |
| DELETE | `/v1/user/avatar` | ✅ User | Kendi avatar'ını sil |
| POST | `/v1/admin/users/:id/avatar` | ✅ Admin | Kullanıcı avatar'ı yükle |
| GET | `/uploads/avatars/{filename}` | ❌ | Avatar görüntüle |
---
## 🎯 Kullanım Akışı
### Normal Kullanıcı
1. Login → Token al
2. POST `/v1/user/avatar` (multipart/form-data ile dosya gönder)
3. Response'da avatar URL gelir
4. Avatar'ı görüntüle: `GET /uploads/avatars/{filename}`
5. İsterseniz DELETE ile silin
### Admin
1. Admin login → Token al
2. POST `/v1/admin/users/{user_id}/avatar` (herhangi bir kullanıcı için)
3. Kullanıcının avatar'ı güncellenir
---
## ✅ Başarı Kriterleri
- ✅ Multipart/form-data ile dosya upload
- ✅ 5MB boyut limiti
- ✅ Format validasyonu
- ✅ Eski avatar otomatik silme
- ✅ Static file serving
- ✅ User + Admin endpoint'leri
- ✅ Authentication & Authorization
**Avatar upload sistemi tam çalışıyor! 🎉**