# 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 ` 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 `` 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 ``` **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 ` 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/.` | | `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 ` 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