Ana içeriğe geç
Ahmet Faruk UzunkayaYaratıcı geliştirici
← Projelere dön
Web ürünüGeliştiriliyorGüncellendi · 8 Ekim 2026

Indie Valley

Türkiye'de oyun yapanları, oynayanları ve oyunlara müzik yapanları bir araya getiren platform.

Oyun tarafında Steam ve IGDB destekli bir kütüphane ve onaylı stüdyo sayfaları, müzik tarafında kendi sunucumuzda barındırdığımız parçalar var. İnsanları buluşturmak için de kaydırmalı eşleşme, anonim sesli tanışma, LiveKit üzerinde canlı sesli odalar ve gerçek zamanlı sohbet kurduk. Projenin kurucu ortağı ve teknik lideriyim.

NestJSFlutterPostgreSQLRedisLiveKit
Indie Valley ekran görüntüsü
Durum
Geliştiriliyor
Kategori
Web ürünü
Güncelleme
8 Ekim 2026
Yığın
NestJSFlutterPostgreSQLRedisLiveKit
Indie Valley

Indie Valley, Türkiye'de oyun yapan, oyun oynayan ve oyunlara müzik yapan insanları bir araya getirmek için kurduğumuz bir platform. Ürünü ortaklarımla birlikte kuruyorum, teknik tarafı da ben yönetiyorum. Proje hâlâ geliştirme aşamasında; o yüzden burada ekran görüntüsü göstermek yerine neyi nasıl kurduğumuzu anlatıyorum.

Platformun üç tarafı var ve her birinin ihtiyacı farklı. Oyuncu birlikte oynayacak birini arıyor, geliştirici oyununu gösterip denetebileceği insanları, müzisyen de dinleyici ve iş. Indie Valley'deki her parça bu üç arayıştan birine karşılık geliyor.

Vadide neler var

Oyun kütüphanesine Steam hesabındaki oyunlar aktarılıyor, eksik bilgiler de IGDB'den tamamlanıyor. Her oyunun durumu, oynama süresi, ilerlemesi, puanı ve incelemesi kayıtlı; topluluğun o oyun hakkındaki istatistikleri de aynı sayfada.

Stüdyo tarafında bağımsız ekipler başvuruyor ve onaylandıktan sonra kendi stüdyo sayfalarını açıyor. Bu sayfada wiki, ekip davetleri, topluluklar ve stüdyonun oyunları var.

Müzik parçalarını kendi sunucumuzda barındırıyoruz. Dinlerken parçanın belli bir anına yorum bırakabiliyorsun; çalma listeleri ve "arkadaşların şu an dinliyor" akışı da var.

Eşleşme kaydırarak yapılıyor. Eşleşme oturumlarını sürümlü tutuyoruz (compare-and-swap), böylece aynı anda gelen iki işlem birbirinin üzerine yazamıyor. Gerektiğinde eşleşmeyi kill switch'lerle kapatabiliyoruz.

Kör sesli tanışmada iki kişi üç dakikalık anonim bir sesli görüşmeye giriyor. Görüşmenin ardından beğen ya da geç seçimi, sonra da karşılıklı açılma adımı geliyor.

Canlı sesli odalar LiveKit üzerinde çalışıyor; el kaldırma ve moderatör işlemleri var. Bağlantısı kopup odada asılı kalan katılımcılar 90 saniye içinde temizleniyor.

Sohbette mesajlar idempotent gönderiliyor: aynı mesaj ağ yüzünden iki kez yollansa da bir kez kaydediliyor. İletildi ve okundu bilgisi ile yazıyor göstergesi de var. İki kişi arasındaki birebir sohbet tek bir anahtara bağlı, bu yüzden aynı ikili için ikinci bir konuşma açılamıyor.

Mimari

Arka uç, NestJS ile yazılmış modüler bir monolit. Yanında Prisma ve PostgreSQL, Redis, gerçek zaman için Socket.IO, ses için LiveKit ve dosyalar için MinIO var. Web istemcisi Next.js ile, mobil uygulama ise iOS ve Android için Flutter ile yazıldı; durum yönetiminde Riverpod kullanıyoruz.

API bugün 28 modül ve 71 Prisma modelinden oluşuyor. 282 uç noktanın OpenAPI anlık görüntüsünü depoda tutuyoruz; bir sözleşme kontrolü de API'nin bu görüntüden sapıp sapmadığına bakıyor. Aynı sözleşme, istemcileri üretmek için de hazır bir temel.

Güvenlik tarafı

Oturumda her yenilemede değişen refresh token'lar kullanıyoruz. Bir refresh token ikinci kez kullanılırsa o token'ın ait olduğu ailenin tamamı iptal ediliyor. Önce çalınmış token mı geldi, meşru kullanıcı mı, fark etmiyor; ikisi de oturumu kaybediyor. Erişim token'ı her istekte, WebSocket bağlantısında ise 30 saniyede bir yeniden doğrulanıyor. Bu yüzden engellenen bir hesabın açık bağlantısı da kısa sürede kapanıyor. Google ve Steam ile girişte PKCE kullanıyoruz.

Medya yüklemesi iki adımlı. İstemci önce imzalı bir adres alıp dosyayı doğrudan depoya yüklüyor. Ardından gelen geri çağrıda dosyanın sahibi, boyutu ve ilk baytlarındaki imza (magic bytes) kontrol ediliyor; dosya ancak bu kontrollerden geçtikten sonra yayına alınıyor. Uzantısı .png olup içinde başka bir şey taşıyan dosya bu adımda eleniyor.

Eşleşmede asıl veri PostgreSQL'de duruyor. Redis yalnızca hız için tutulan, baştan kurulabilen bir indeks; Redis düşerse sistem güvenli tarafta kalıp kapanıyor. Kişisel veriler de loglara yazılmadan önce temizleniyor.

Küçük ama önemli kararlar

Beğenme ve kaydetme için aç/kapa (toggle) uçları yerine durumu bildiren PUT ve DELETE istekleri kullanıyoruz. İstemci doğrudan "beğenildi" ya da "beğenilmedi" diyor; aynı istek iki kez gelse de sonuç değişmiyor ve ekrandaki durum sunucudakinin tersine dönemiyor. Arayüz bu işlemlerde sunucuyu beklemeden iyimser şekilde güncelleniyor, hata olursa eski haline dönüyor.

Flutter uygulamasındaki 1.013 metnin hepsi Türkçe ve İngilizce olarak çevrili. Cihazında azaltılmış hareket tercihi açıksa animasyonlar her ekranda buna uyuyor. Web tarafında misafirler kayıt duvarına takılmadan gezebiliyor. Akıştaki her gönderi kendi hata sınırında çiziliyor, yani bozuk bir gönderi sayfanın geri kalanını da beraberinde götürmüyor. Paylaşım için kısa linkler, OG görselleri ve JSON-LD de hazır.

Bu kararların bir kısmını Indie Valley yazı dizisinde daha ayrıntılı anlatıyorum.