Pinwise REST API

Gece gündüz seyahatlerinizi, mekanlarınızı ve notlarınızı akıllıca yönetin. Yapay zeka destekli, konum tabanlı kişisel rehberinizin geliştirici dokümantasyonu.

Detaylı Teknoloji & Mimari Altyapısı

Pinwise backend'i, sektör standartlarında kurumsal kalitede bir altyapı sunmak üzere en modern .NET yaklaşımlarıyla donatılmıştır.

🏗️

Clean Architecture

Uygulama Domain, Application, Infrastructure ve API olmak üzere soyutlanmış 4 temel katmana ayrılmıştır. Bu sayede merkezdeki iş kuralları (Application/Domain), dış dünyadan (Veritabanı, Web API vb.) tamamen izoledir.

Domain Driven Design (DDD): Entity'ler sadece veri tutmaz, iş mantığı barındırır (Rich Entity modeli).
Dependency Inversion: Arayüzler Application'da tanımlanır, Infrastructure katmanında implemente edilir.
CQRS Benzeri Yapı: Servisler tek sorumluluk prensibiyle (SRP) çalışır ve DTO'lar aracılığıyla haberleşir.
🌍

PostgreSQL + PostGIS

Mekan koordinatları (Enlem/Boylam) veritabanında "Point" veri tipinde saklanır. Bu sayede metinsel karşılaştırmalar yerine matematiksel-coğrafi işlemler veritabanı motoru seviyesinde halledilir.

NetTopologySuite: C# tarafında verilerin coğrafi nesnelere dönüştürülmesini sağlar.
Performanslı Uzamsal Sorgu: EF Core üzerinden ST_Distance fonksiyonları ile "Yakındakileri Bul" saniyelerden milisaniyelere iner.
🛡️

Firebase Auth & Güvenlik

Kullanıcı yönetimi ve şifreleme Firebase'e delege edilmiştir. API sadece Bearer JWT token'ları dinler.

Stateless Güvenlik: Sunucuda Session tutulmaz. Firebase token'ı arka planda Google tarafında doğrulanır.
Kesin İzolasyon: Tüm DB sorgularında currentUserId kuralı otomatik çalışır, başka hesabın verisine asla erişilemez.
⚙️

Modern Araçlar

Controller'ların şişmesini engellemek ve kod okunaklığını maksimal seviyeye çıkarmak için modern araçlar kullanılmıştır.

FluentValidation: İstekler (DTO'lar) API'ye ulaşmadan ara katmanda denetlenir, anında HTTP 400 Validation Error tetiklenir.
Mapster: Entity & DTO dönüşümleri Compile-time optimizasyonu ile (Code Generation) %40 daha hızlı ve GC (Garbage Collector) dostu yapılır.
🐳

Docker & Containerization

Uygulamanın farklı bileşenleri ve veritabanı Docker Compose ağı ile sarmalanmıştır. "Benim makinemde çalışıyordu" problemi tamamen ortadan kalkar.

Çevresel Stabilite: Tüm uygulama bağımlılıkları ve Runtime sürümü imajın içindedir, her cihazda ve sunucuda birebir aynı ayağa kalkar.
Pratik Geliştirme: Projeyi kurmak ve PostGIS veritabanını test etmek için sadece docker-compose up komutunu çalıştırmak yeterlidir.
⏱️

Hangfire & Arkaplan İşleri

Uzun sürebilen yapay zeka işlem süreçleri (mekanların AI ile analiz edilmesi, tag oluşturulması vb.) asenkron olarak Hangfire Dashboard yönetimindeki güvenilir kuyruklara (Queue) atılır.

Asenkron Mimari: API istemciyi bekletmez (Hemen 202 Accepted döner), devasa hesaplamalar arkada HTTP Request Thread'lerini işgal etmeden akar.
Automatic Retry: AI servisleri kilitlense bile işler (Jobs) kaybolmaz, takvime göre sorun yaşanmadan otomatik olarak tekrar denenir.

Örnek Uygulama Senaryoları (Use Cases)

Geliştiricilerin Pinwise API'sini kullanarak oluşturabileceği örnek kullanıcı senaryoları.

🗺️ Senaryo 1: Yakın Çevre Keşfi

1
Kullanıcı uygulamayı açar ve anlık konumuna izin verir.
2
Uygulama GET /api/v1/Places/nearby isteğini enlem/boylam vererek atar.
3
DB'de PostGIS devrede girer, 5 kilometrelik çaptaki mekanları mesafelerine göre listeler.
4
Ekranda "Sana 320m uzaklıkta" olarak görselleştirilir.

📁 Senaryo 2: Seyahat Planlama

1
Kullanıcı POST /api/v1/Collections idarecisi ile "Roma Gezisi" klasörü yaratır.
2
Arama modülüyle Roma'daki Colosseum vb. yerleri sorgular.
3
Beğendiği mekan UUID'ini, Koleksiyon UUID'inin içine "Add Place" API'si ile ekler.
4
Roma'ya vardığında tek bir klasörde sadece o yerleri listeler.

📝 Senaryo 3: Check-in ve Belgeleme

1
Kullanıcı önceden kaydettiği bir restoranda siparişini verir.
2
Uygulamada yemeğin fiyatını ve "Kahvesi güzeldi" notunu yazar.
3
Uygulama bu mekanı "Mark As Visited" API'si ile ziyaret tarihlendirir.
4
Artık bu mekan "Gitmek İstediklerim" listesinden düşer.

Kapsamlı API Referansı (v1)

Genişletilmiş dokümantasyon. Tüm başlıklara tıklayarak açabilirsiniz. Her istekte Header: Authorization: Bearer {FirebaseToken} zorunludur.

📍 Places (Mekan) İşlemleri

GET /api/v1/Places Tüm mekanları listeler (Paged)

Query Parametreleri

Parametre Tip Açıklama
pageNumber int Sayfa numarası (Varsayılan: 1)
pageSize int Sayfa başına kayıt sayısı (Varsayılan: 20)

JSON Response (200 OK)

{
  "success": true,
  "data": {
    "items": [
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "Blue Bottle Coffee",
        "latitude": 41.0289,
        "longitude": 28.9743,
        "category": "Cafe",
        "status": "Saved", // Enum: Saved, Visited, Archived
        "processingStatus": "Completed" // Enum: Pending, Completed, Failed
      }
    ],
    "pageNumber": 1,
    "pageSize": 20,
    "totalCount": 124,
    "totalPages": 7,
    "hasNextPage": true,
    "hasPreviousPage": false
  },
  "errors": null
}
GET /api/v1/Places/nearby Konuma göre yakındaki mekanlar

Query Parametreleri

Parametre Tip Açıklama
latitude * double Geçerli Kullanıcı Enlemi
longitude * double Geçerli Kullanıcı Boylamı
radiusInMeters int Yarıçap (Metre). Varsayılan: 5000
pageNumber int Sayfa No
pageSize int Adet

JSON Response (200 OK)

{
  "success": true,
  "data": {
    "items": [
      {
        "id": "e023...",
        "name": "Galata Kulesi",
        "distance": 850.5 // PostGIS tarafından metre cinsi dönen özel hesaplanmış alan
      }
    ],
    "pageNumber": 1,
    "pageSize": 20,
    "totalCount": 3
  }
}
GET /api/v1/Places/search Filtreleme ve arama mekanizması

Query Parametreleri

Parametre Tip Açıklama
searchTerm string Mekan adı veya açıklamasında `%like%` araması yapar.
category string Kategori tam eşleşme arar.
city string Şehir eşleşmesi arar.
status string Durum filtresi (Örn: `Visited` gönderilirse sadece ziyaret edilenler gelir).

(Aynı Pagination yapısı döner)

GET /api/v1/Places/{id} Tek bir mekanın detayını getirir

URL Parametreleri

id * guid UUID formatında mekan kimliği

JSON Response (200 OK)

{
  "success": true,
  "data": {
    "id": "3fa85f64...",
    "name": "Minoa Village",
    "city": "Istanbul",
    "costLevel": 3,
    "aiRawJson": "{...yZekaAnalizSonucu...}",
    "tags": [ "Sakin", "Çalışmaya Uygun" ],
    "notes": [],
    "expenses": []
  }
}
POST /api/v1/Places Yeni mekan kaydı oluşturur (Validation Devrede)

Request Body JSON

{
  "name": "Petra Roasting", // Zorunlu (Min 2 karakter)
  "latitude": 41.0503, // Zorunlu (-90 ile +90 arası)
  "longitude": 29.0065, // Zorunlu (-180 ile +180 arası)
  "description": "Kahvesi meşhur, bilgisayarla gidilir.", // Opsiyonel
  "category": "Coffee", // Opsiyonel
  "sourceUrl": "https://maps.google.com/..." // Opsiyonel (AI işlenmesi için verilebilir)
}

JSON Response (202 Accepted)

{
  "success": true,
  "data": {
    "id": "...",
    "processingStatus": "PendingAiAnalysis" // Arka plan işi tetiklendi
  }
}
PUT /api/v1/Places/{id}/visit Mekanı Ziyaret Edildi moduna alır

URL Parametresi

{id}: İşlem yapılacak mekanın UUID'si

Request Body JSON

{
  "visitedAt": "2024-03-24T18:30:00Z" // UTC Formatında ISO-8601 Tarihi (Zorunlu)
}

JSON Response (200 OK)

Mekanın güncellenmiş DTO hali döner (Status = "Visited" olarak gelir).

📁 Collections (Koleksiyon) İşlemleri

GET /api/v1/Collections Kullanıcının klasörlerini listeler

JSON Response (200 OK)

{
  "success": true,
  "data": [
    {
      "id": "d9f10a...",
      "name": "Moda Gece Kulüpleri",
      "description": "Sadece rock çalanlar",
      "itemCount": 3 // Koleksiyonun içindeki yerlerin sayısı
    }
  ]
}
GET /api/v1/Collections/{id} Koleksiyon detayını getirir

URL Parametreleri

{id}: Koleksiyon UUID

JSON Response (200 OK)

{
  "success": true,
  "data": {
    "id": "d9f10a...",
    "name": "...",
    "places": [
      { "id": "...", "name": "..." } // Bağlı mekanların listesi
    ]
  }
}
POST /api/v1/Collections Yeni klasör oluşturur

Request Body JSON

{
  "name": "İş Yemekleri", // Zorunlu (Max 100 karakter)
  "description": "Müşterilerle gidilecek lüks restoranlar" // Opsiyonel
}

Response

201 Created

PUT /api/v1/Collections/{id} Koleksiyon Meta-Datasını Günceller

URL Parametresi

{id}: Modifiye edilecek klasörün UUID'si

Request Body JSON

{
  "name": "Değiştirilen İsim",
  "description": "Yeni açıklama eklendi"
}

Response

204 No Content

DELETE /api/v1/Collections/{id} Koleksiyonu siler
Dikkat: Sadece Collection (Klasör) ve CollectionItem (Bağ) tablolarından kayıt silinir. Klasör içerisindeki Places (Mekanlar) ana veritabanında kalmaya devam eder.

Response

204 No Content

POST /api/v1/Collections/{id}/places/{placeId} Mekanı Klasöre Taşır (Bağlar)

URL Parametreleri

id * guid Koleksiyon Kimliği (Örn: "Avrupa Turu" klasör id'si)
placeId * guid Mekan Kimliği (Örn: "Galata Kulesi" mekan id'si)

RequestBody gönderilmez. Doğrudan URL üzerinden işlem yapılır.

Response

204 No Content

DELETE /api/v1/Collections/{id}/places/{placeId} Bağı Koparır (Klasörden Çıkar)

URL Parametreleri

id * guid Koleksiyon Kimliği
placeId * guid Ayrılacak Mekan Kimliği

(Aynı şekilde sadece bağ tablosundaki satır silinir. Mekan var olmaya devam eder.)

Response

204 No Content

Standart Hata Yönetimi

API, öngörülebilir tüm hataları standart bir Exception Handling yapısıyla döner. Hata olması durumunda "success": false ve "errors": [...] objesi dönülür.

HTTP 400 Bad Request / Validation Error
Gönderilen JSON istek kurallara uymadığında (Örn: `name` alanı boş veya `latitude` geçersiz bir değer vb.) FluentValidation tarafından fırlatılır. İçerisinde hangi alanın neden geçersiz olduğuna dair net mesajlar barındıran dizi içerir.
HTTP 401 Unauthorized
İstek header'ında Firebase Bearer Token gönderilmediğinde veya token süresi dolduğunda (.NET yetkilendirme katmanında) kesilir. İstemci kullanıcıyı Login ekranına atmalıdır.
HTTP 403 Forbidden
Kullanıcı giriş yapmıştır (Token geçerlidir) fakat gönderdiği `id` kendisine ait olmayan bir kayda aittir. Database tarafında Strict Tenant Isolation yapıldığından işlem reddedilir.
HTTP 404 Not Found
URL'de veya Route'da belirtilen `id` veritabanında hiç bulunamadığında dönülür. Custom Exception fırlatılarak standartlaştırılmıştır.
HTTP 500 Internal Server Error
Sunucu veya altyapı tarafında kriz yaratan, fırlatılmayan bir hata oluştuğunda Global Middleware araya girer. İçeriği loglar ve istemciye güvenli, sızdırma içermeyen bir problem mesajı döner.