# Go Image Manipulation API

Fiber v3, GORM (MySQL) ve libvips/bimg tabanlı, JWT kimlik doğrulamalı resim yükleme ve işleme REST API.

---

## İçindekiler

- [Özellikler](#özellikler)
- [Teknoloji Yığını](#teknoloji-yığını)
- [Proje Yapısı](#proje-yapısı)
- [Gereksinimler](#gereksinimler)
- [Kurulum ve Çalıştırma](#kurulum-ve-çalıştırma)
- [Ortam Değişkenleri](#ortam-değişkenleri)
- [API Referansı](#api-referansı)
  - [Auth](#auth)
  - [Images](#images)
  - [Admin](#admin)
  - [Static Files](#static-files)
- [Kimlik Doğrulama](#kimlik-doğrulama)
- [Veri Modelleri](#veri-modelleri)
- [Resim İşleme](#resim-işleme)
- [Swagger UI](#swagger-ui)
- [Testler](#testler)
- [Deployment](#deployment)

---

## Özellikler

| Özellik | Detay |
|---------|-------|
| 🔐 JWT Auth | Access token (15 dk) + Refresh token (7 gün), `role` claim içerir |
| 🖼️ Resim Yükleme | UUID tabanlı isim, otomatik format dönüşümü |
| ⚙️ Resim İşleme | Yeniden boyutlandırma, kırpma, cover modu, kalite, format değişimi |
| 🗂️ Resim Listeleme | Sayfalı listeleme, tek resim detayı |
| 🌐 Statik Erişim | Yüklenen resimlere doğrudan URL ile erişim |
| 🛡️ Admin Panel | Kullanıcıya API token atama, tüm resimleri listeleme |
| 🗄️ MySQL + GORM | Otomatik migrasyon, soft delete |
| ⚡ Redis | Bağlantı havuzu hazır |
| 📄 Swagger UI | `/swagger` adresinde interaktif API belgesi |

---

## Teknoloji Yığını

| Katman | Kütüphane/Araç |
|--------|---------------|
| HTTP Framework | [Fiber v3](https://github.com/gofiber/fiber) |
| ORM | [GORM](https://gorm.io) + MySQL driver |
| Resim İşleme | [bimg](https://github.com/h2non/bimg) (libvips bağlayıcısı) |
| JWT | [golang-jwt/jwt v5](https://github.com/golang-jwt/jwt) |
| Redis | [go-redis/redis v9](https://github.com/redis/go-redis) |
| Parola Hashing | bcrypt |
| UUID | google/uuid |
| Swagger | swaggo/swag |
| Env Yönetimi | godotenv |

---

## Proje Yapısı

```
goimgApi/
├── main.go                   # Uygulama başlangıç noktası
├── go.mod / go.sum
├── .env                      # Ortam değişkenleri (Git'e eklenmez)
│
├── accounts/                 # Kimlik doğrulama & kullanıcı yönetimi
│   ├── models.go             # User struct (GORM modeli)
│   ├── handlers.go           # Register, Login, Refresh, Middleware, Admin handlers
│   ├── jwt.go                # Token üretimi & parse, rol normalizasyonu
│   ├── accounts_test.go      # JWT & rol testleri
│   ├── jwt_test.go           # Token claim testleri
│   └── models/               # İkincil model paketi (sosyal hesap, profil, refresh token)
│       ├── account.go
│       ├── token.go
│       └── models_test.go
│
├── images/                   # Resim yükleme & işleme
│   ├── models.go             # Image struct (GORM modeli)
│   ├── handlers.go           # Upload, ListImages, GetImage, AdminListImages, Process
│   ├── processor.go          # bimg işleme motoru
│   └── handlers_test.go      # Path yardımcı & sayfalama testleri
│
├── configs/                  # Bağlantı yapılandırmaları
│   ├── db.go                 # MySQL bağlantısı, CORS/rate-limit seed yardımcıları
│   ├── redis.go              # Redis bağlantısı
│   └── configs_test.go       # Yardımcı fonksiyon testleri
│
├── router/
│   ├── routers.go            # Tüm route tanımları, statik dosya sunumu
│   └── routers_test.go       # Swagger & statik route testleri
│
├── docs/                     # Swaggo tarafından üretilen Swagger dosyaları
│   ├── docs.go
│   ├── swagger.json
│   ├── swagger.yaml
│   └── docs_test.go
│
├── uploads/                  # Yüklenen resimlerin depolandığı dizin
└── tmp/                      # Geliştirme sırasında kullanılan geçici scriptler
```

---

## Gereksinimler

- **Go 1.26+**
- **MySQL 8.0+**
- **Redis 6+**
- **libvips 8.8+** (bimg için)

### libvips Kurulumu

```bash
# Ubuntu / Debian
sudo apt-get install -y libvips-dev

# macOS
brew install vips

# Arch Linux
sudo pacman -S libvips
```

---

## Kurulum ve Çalıştırma

### 1. Depoyu klonlayın

```bash
git clone https://github.com/kullanici/goimgApi.git
cd goimgApi
```

### 2. Bağımlılıkları yükleyin

```bash
go mod download
```

### 3. Ortam değişkenlerini ayarlayın

```bash
cp .env.example .env
# .env dosyasını düzenleyin
```

### 4. Swagger spec'ini üretin

```bash
# swag CLI kurulumu (tek seferlik)
go install github.com/swaggo/swag/cmd/swag@latest

# docs/ klasörünü yeniden üret
swag init --parseDependency --parseInternal
```

> ⚠️ **Önemli:** Yeni bir endpoint ekledikten veya mevcut bir endpoint'in godoc yorumunu değiştirdikten sonra bu komutu **mutlaka** tekrar çalıştırın; aksi hâlde Swagger UI güncellenmiş endpoint'leri göstermez.

### 5. Uygulamayı başlatın

```bash
go run .
```

Sunucu `http://localhost:8080` adresinde başlar.

### Geliştirme modunda (hot-reload)

```bash
# air kurulumu (opsiyonel)
go install github.com/air-verse/air@latest
air
```

---

## Ortam Değişkenleri

Proje kök dizininde bir `.env` dosyası oluşturun:

```dotenv
# ─── Veritabanı (MySQL) ──────────────────────────────────────────────────────
DB_HOST=localhost
DB_PORT=3306
DB_USER=go_imgapi
DB_PASSWORD=your_password
DB_NAME=go_imgapi

# ─── Redis ───────────────────────────────────────────────────────────────────
REDIS_URL=redis://default:your_password@localhost:6379/0
# veya ayrı ayrı:
# REDIS_HOST=localhost
# REDIS_PORT=6379
# REDIS_PASSWORD=

# ─── JWT ─────────────────────────────────────────────────────────────────────
JWT_SECRET=en-az-32-karakter-uzun-gizli-anahtar
JWT_REFRESH_SECRET=farkli-bir-gizli-anahtar-refresh-icin

# ─── Sunucu ──────────────────────────────────────────────────────────────────
PORT=8080

# ─── CORS Bootstrap (opsiyonel) ──────────────────────────────────────────────
CORS_BOOTSTRAP_WHITELIST_ORIGINS=http://localhost:3000,https://yourfrontend.com

# ─── Rate Limit Bootstrap (opsiyonel) ────────────────────────────────────────
RL_BOOTSTRAP_LOGIN_MAX_REQUESTS=10
RL_BOOTSTRAP_LOGIN_WINDOW_SECONDS=60
RL_BOOTSTRAP_API_MAX_REQUESTS=120
RL_BOOTSTRAP_API_WINDOW_SECONDS=60
```

> **Not:** `JWT_SECRET` ve `JWT_REFRESH_SECRET` birbirinden farklı ve en az 32 karakter olmalıdır.

---

## API Referansı

Base URL: `http://localhost:8080`

### Auth

#### `POST /auth/register` — Kayıt

Yeni kullanıcı oluşturur.

**İstek** (`multipart/form-data`)

| Alan | Tür | Zorunlu | Açıklama |
|------|-----|---------|----------|
| `email` | string | ✅ | Geçerli e-posta adresi |
| `password` | string | ✅ | En az 6 karakter |

**Başarılı Yanıt** `201 Created`

```json
{
  "message": "User registered",
  "user_id": 1
}
```

**Hata Yanıtları**

| Kod | Açıklama |
|-----|----------|
| `400` | Email boş / şifre 6 karakterden kısa / email zaten kullanımda |

---

#### `POST /auth/login` — Giriş

Kullanıcıyı doğrular ve JWT token çifti döner.

**İstek** (`multipart/form-data`)

| Alan | Tür | Zorunlu |
|------|-----|---------|
| `email` | string | ✅ |
| `password` | string | ✅ |

**Başarılı Yanıt** `200 OK`

```json
{
  "access_token": "eyJhbGci...",
  "refresh_token": "eyJhbGci...",
  "user": {
    "id": 1,
    "email": "kullanici@ornek.com",
    "role": "admin"
  }
}
```

> `role` değeri `"admin"` veya `"user"` olur.

**Hata Yanıtları**

| Kod | Açıklama |
|-----|----------|
| `401` | Geçersiz e-posta veya şifre |

---

#### `POST /auth/refresh` — Token Yenile

Geçerli bir refresh token ile yeni token çifti üretir.
Refresh sırasında kullanıcının güncel rolü DB'den yeniden okunur.

**İstek** (`multipart/form-data`)

| Alan | Tür | Zorunlu |
|------|-----|---------|
| `refresh_token` | string | ✅ |

**Başarılı Yanıt** `200 OK`

```json
{
  "access_token": "eyJhbGci...",
  "refresh_token": "eyJhbGci..."
}
```

**Hata Yanıtları**

| Kod | Açıklama |
|-----|----------|
| `401` | Refresh token eksik, geçersiz veya süresi dolmuş |
| `401` | Token'a ait kullanıcı bulunamadı (silinmiş olabilir) |

---

### Images

Tüm `/images` endpoint'leri `Authorization: Bearer <access_token>` başlığı gerektirir.

#### `POST /images` — Resim Yükle

**İstek** (`multipart/form-data`)

| Alan | Tür | Zorunlu | Açıklama |
|------|-----|---------|----------|
| `image` | file | ✅ | Resim dosyası |
| `w` | int | | Hedef genişlik (px) |
| `h` | int | | Hedef yükseklik (px) |
| `q` | int | | Kalite 1-100 (varsayılan: 85) |
| `f` | string | | Format: `webp`, `avif`, `png`, `jpg` |
| `mode` | string | | `cover` — kırparak doldur |

**Başarılı Yanıt** `201 Created`

```json
{
  "message": "Image uploaded successfully",
  "image_id": 12,
  "filename": "550e8400-e29b-41d4-a716-446655440000.webp",
  "public_path": "/uploads/550e8400-e29b-41d4-a716-446655440000.webp",
  "image_url": "http://localhost:8080/uploads/550e8400-e29b-41d4-a716-446655440000.webp"
}
```

> **`public_path` vs `image_url` farkı:**
> - `public_path` — domain bağımsız, DB'ye kaydedilen göreli yol (`/uploads/...`). Farklı ortamlarda (staging, prod, CDN) taşınabilir.
> - `image_url` — isteği yapan host'a göre dinamik üretilen tam URL. `X-Forwarded-Host` / `X-Forwarded-Proto` başlıkları varsa onları kullanır (proxy/CDN için).
>
> Frontend'de resmi göstermek için `image_url` değerini doğrudan kullanabilir ya da `API_BASE_URL + public_path` formülünü tercih edebilirsiniz.

---

#### `GET /images` — Resimleri Listele

Kimliği doğrulanmış kullanıcının resimlerini sayfalı döner.

**Query Parametreleri**

| Parametre | Varsayılan | Maks | Açıklama |
|-----------|-----------|------|----------|
| `page` | `1` | — | Sayfa numarası |
| `limit` | `20` | `100` | Sayfa başına kayıt |

**Başarılı Yanıt** `200 OK`

```json
{
  "data": [
    {
      "id": 12,
      "user_id": 1,
      "filename": "550e8400....webp",
      "public_path": "/uploads/550e8400....webp",
      "image_url": "http://localhost:8080/uploads/550e8400....webp",
      "mime_type": "image/jpeg",
      "size_kb": 42,
      "width": 800,
      "height": 600,
      "quality": 85,
      "format": "webp",
      "mode": "original",
      "created_at": "2026-04-10T01:00:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20
}
```

---

#### `GET /images/:id` — Tek Resim Detayı

Kimliği doğrulanmış kullanıcıya ait belirli bir resmin detayını döner.

**Başarılı Yanıt** `200 OK`

Listeleme endpoint'indeki tek kayıt yapısı ile aynı.

**Hata Yanıtları**

| Kod | Açıklama |
|-----|----------|
| `404` | Resim bulunamadı veya kullanıcıya ait değil |

---

#### `GET /images/:id/process` — Resmi İşle ve Döndür

API token ile korunur (JWT gerektirmez). Web sitelerinde `<img src="...">` ile doğrudan kullanım için tasarlanmıştır.

**Query Parametreleri**

| Parametre | Zorunlu | Açıklama |
|-----------|---------|----------|
| `token` | ✅ | Kullanıcıya ait API token |
| `w` | | Hedef genişlik (px) |
| `h` | | Hedef yükseklik (px) |
| `q` | | Kalite 1-100 |
| `f` | | Format: `webp`, `avif`, `png`, `jpg` |
| `mode` | | `cover` — kırparak doldur |

**Başarılı Yanıt** `200 OK` — İşlenmiş resim binary verisi (`image/webp`, `image/jpeg`, vb.)

**Örnek Kullanım**

```html
<!-- Orijinal resmi göster -->
<img src="http://localhost:8080/images/12/process?token=API_TOKEN">

<!-- 400×300 WebP olarak göster -->
<img src="http://localhost:8080/images/12/process?token=API_TOKEN&w=400&h=300&f=webp">

<!-- Cover kırpma ile 200×200 thumbnail -->
<img src="http://localhost:8080/images/12/process?token=API_TOKEN&w=200&h=200&mode=cover&f=webp">
```

**Hata Yanıtları**

| Kod | Açıklama |
|-----|----------|
| `401` | Token eksik, geçersiz veya süresi dolmuş |
| `404` | Resim bulunamadı |
| `500` | Dosya okunamadı veya işleme hatası |

---

### Admin

Tüm `/admin` endpoint'leri hem `Authorization: Bearer <access_token>` hem de admin yetkisi gerektirir.

#### `POST /admin/users/:id/api-token` — API Token Oluştur

Belirtilen kullanıcıya yeni bir API token atar.

**Path Parametreleri**

| Parametre | Tür | Açıklama |
|-----------|-----|----------|
| `id` | int | Hedef kullanıcı ID |

**İstek** (`multipart/form-data` veya query)

| Alan | Tür | Açıklama |
|------|-----|----------|
| `expires_in_days` | int | Kaç gün geçerli (0 = süresiz) |

**Başarılı Yanıt** `200 OK`

```json
{
  "message": "API token created successfully",
  "api_token": "550e8400-e29b-41d4-a716-446655440000",
  "expires_at": "2026-05-10T01:00:00Z"
}
```

---

#### `GET /admin/images` — Tüm Resimleri Listele

Tüm kullanıcılara ait resimleri sayfalı döner.

**Query Parametreleri**

| Parametre | Açıklama |
|-----------|----------|
| `page` | Sayfa numarası (varsayılan: 1) |
| `limit` | Sayfa başına kayıt (varsayılan: 20, maks: 100) |
| `user_id` | Belirli bir kullanıcıya göre filtrele |

**Başarılı Yanıt** `200 OK`

```json
{
  "data": [
    {
      "id": 12,
      "user_id": 3,
      "filename": "550e8400-e29b-41d4-a716-446655440000.webp",
      "public_path": "/uploads/550e8400-e29b-41d4-a716-446655440000.webp",
      "image_url": "http://localhost:8080/uploads/550e8400-e29b-41d4-a716-446655440000.webp",
      "mime_type": "image/jpeg",
      "size_kb": 42,
      "width": 800,
      "height": 600,
      "quality": 85,
      "format": "webp",
      "mode": "original",
      "created_at": "2026-04-10T01:00:00Z"
    }
  ],
  "total": 50,
  "page": 1,
  "limit": 20
}
```

---

### Static Files

#### `GET /uploads/:filename` — Yüklenen Resme Eriş

Yüklenen resimlere doğrudan URL ile erişim sağlar. Path traversal saldırılarına karşı korumalıdır.

```
GET /uploads/550e8400-e29b-41d4-a716-446655440000.webp
```

---

## Kimlik Doğrulama

### JWT Token Yapısı

```json
{
  "user_id": 1,
  "role": "admin",
  "exp": 1775771479,
  "iat": 1775770579
}
```

| Claim | Açıklama |
|-------|----------|
| `user_id` | Kullanıcı birincil anahtarı |
| `role` | `"admin"` veya `"user"` |
| `exp` | Token son geçerlilik zamanı (Unix timestamp) |
| `iat` | Token üretim zamanı |

### Token Ömürleri

| Token | Ömür |
|-------|------|
| Access Token | **15 dakika** |
| Refresh Token | **7 gün** |
| API Token | Admin tarafından belirlenir (sınırsız veya belirli gün) |

### İstek Başlığı

```http
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

### Token Yenileme Akışı

```
POST /auth/login
  → access_token (15 dk) + refresh_token (7 gün)

access_token süresi dolunca:
  POST /auth/refresh  body: refresh_token=...
  → yeni access_token + yeni refresh_token
```

---

## Veri Modelleri

### `User` (accounts paketi)

| Alan | Tür | Açıklama |
|------|-----|----------|
| `id` | uint | Birincil anahtar |
| `email` | string | Benzersiz, zorunlu |
| `password_hash` | string | bcrypt (JSON'da gizli) |
| `is_admin` | bool | Admin yetkisi (varsayılan: false) |
| `api_token` | string | Resim işleme için API token |
| `api_token_expires_at` | *time | Token bitiş zamanı (null = süresiz) |
| `created_at` | time | |
| `updated_at` | time | |
| `deleted_at` | gorm.DeletedAt | Soft delete |

### `Image` (images paketi)

| Alan | Tür | Açıklama |
|------|-----|----------|
| `id` | uint | Birincil anahtar |
| `user_id` | uint | Sahibi (indexed) |
| `filename` | string | UUID tabanlı dosya adı |
| `public_path` | string | `/uploads/<uuid>.<ext>` |
| `mime_type` | string | Orijinal MIME türü |
| `size` | int64 | KB cinsinden boyut |
| `width` | int | Piksel genişlik |
| `height` | int | Piksel yükseklik |
| `quality` | int | 1–100 arası kalite değeri |
| `format` | string | bimg format adı |
| `mode` | string | `original` veya `cover` |
| `created_at` | time | |

---

## Resim İşleme

Resimler [libvips](https://libvips.github.io/libvips/) ile işlenir. Desteklenen işlemler:

| İşlem | Parametre | Örnek |
|-------|-----------|-------|
| Genişlik ayarla | `w=400` | 400px genişliğe küçült |
| Yükseklik ayarla | `h=300` | 300px yüksekliğe küçült |
| Kalite ayarla | `q=75` | %75 kalite |
| Format dönüştür | `f=webp` | WebP formatına çevir |
| Cover kırpma | `mode=cover` | Oranı koruyarak kırp ve doldur |

### Desteklenen Formatlar

| Format | Parametre Değeri | MIME Türü |
|--------|-----------------|-----------|
| JPEG | `jpg` veya `jpeg` | `image/jpeg` |
| PNG | `png` | `image/png` |
| WebP | `webp` | `image/webp` |
| AVIF | `avif` | `image/avif` |

### Yükleme Sırasında İşleme

Upload endpoint'ine `w`, `h`, `f`, `q` parametreleri verilirse resim **diske yazılmadan önce** işlenir:

```bash
curl -X POST http://localhost:8080/images \
  -H "Authorization: Bearer TOKEN" \
  -F "image=@foto.jpg" \
  -F "w=800" \
  -F "f=webp" \
  -F "q=85"
```

---

## Swagger UI

Tarayıcıda aç:

```
http://localhost:8080/swagger
```

Swagger JSON spec:

```
http://localhost:8080/docs/swagger.json
```

Spec içindeki `host` ve `schemes` alanları **çalışma zamanında** dinamik olarak kaldırılır; böylece farklı ortamlarda (proxy, HTTPS, CDN) Swagger UI her zaman mevcut host'a istek atar.

Swagger'dan istek atmak için:
1. `/auth/login` endpoint'ini çalıştırın
2. Dönen `access_token`'ı kopyalayın
3. Sağ üstteki **Authorize** butonuna tıklayın
4. `Bearer <token>` formatında girin

---

## Testler

```bash
# Tüm paket testleri
go test ./...

# Detaylı çıktı
go test -v ./...

# Belirli paket
go test -v ./accounts
go test -v ./images
go test -v ./router
go test -v ./configs
go test -v ./docs
go test -v ./accounts/models
```

### Test Kapsamı

| Paket | Test Sayısı | Kapsam |
|-------|-------------|--------|
| `accounts` | 15 | JWT üretimi, parse, rol normalizasyonu, `roleFromUser`, User model |
| `accounts/models` | 6 | `IsEmailVerified`, JSON tag güvenliği, GORM tag, `RefreshToken` alanları |
| `configs` | 27 | `normalizeOrigin` (8), `parseOriginList` (4), `envIntOr`/`envInt64Or` (8), bootstrap (7) |
| `docs` | 10 | `SwaggerInfo` alanları, şablon içeriği, swaggo registry kaydı |
| `images` | 9 | Path yardımcıları, `buildImageURL` (forwarded header), sayfalama |
| `router` | 2 | Swagger JSON host/scheme kaldırma, statik dosya servisi |

> Veritabanı veya Redis bağlantısı gerektiren testler mevcut değildir; tüm testler izole ve bağımsız çalışır.

---

## Swagger Spec Güncelleme

Yeni endpoint eklendiğinde veya mevcut godoc yorumu değiştirildiğinde spec'i yeniden üretmek gerekir:

```bash
swag init --parseDependency --parseInternal
```

Bu komut şu dosyaları günceller:

```
docs/
├── docs.go        ← Go kodu (swaggo runtime için)
├── swagger.json   ← Swagger UI'ın okuduğu spec
└── swagger.yaml   ← YAML formatı
```

---

## Deployment

### Docker ile

```dockerfile
FROM golang:1.26-alpine AS builder
RUN apk add --no-cache vips-dev build-base
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o goimgApi .

FROM alpine:3.19
RUN apk add --no-cache vips
WORKDIR /app
COPY --from=builder /app/goimgApi .
COPY --from=builder /app/docs ./docs
RUN mkdir -p uploads
EXPOSE 8080
CMD ["./goimgApi"]
```

```bash
docker build -t goimgapi .
docker run -p 8080:8080 --env-file .env goimgapi
```

### Nginx Reverse Proxy Örneği

```nginx
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        client_max_body_size 50M;
    }
}
```

> `X-Forwarded-Proto` ve `X-Forwarded-Host` başlıkları, upload response'unda dönen `image_url` değerinin doğru domain ile üretilmesi için gereklidir.

### Production için .env Kontrol Listesi

- [ ] `JWT_SECRET` — en az 64 karakter, rastgele üretilmiş
- [ ] `JWT_REFRESH_SECRET` — JWT_SECRET'tan farklı, en az 64 karakter
- [ ] `DB_PASSWORD` — güçlü, production şifresi
- [ ] `CORS_BOOTSTRAP_WHITELIST_ORIGINS` — yalnızca gerçek frontend domain'leri
- [ ] `uploads/` dizini uygulama tarafından yazılabilir (`chmod 755`)

---

## Katkı

1. Fork'layın
2. Feature branch oluşturun: `git checkout -b feature/ozellik-adi`
3. Testler ekleyin ve geçtiğinden emin olun: `go test ./...`
4. Pull request açın

---

## Lisans

MIT

