# LikaWEB Çoklu Site (Multi-Site / Multi-Tenant) Mimari Tasarım Raporu

Bu rapor, mevcut **LikaWEB** Laravel projesini tek bir kod tabanı (codebase) ve tek bir veritabanı üzerinden birden fazla siteyi (**likayazilim.com**, **likaone.com**, **firsatharitasi.com**, **marketkurdu.com** vb.) yönetecek şekilde dönüştürmek için hazırlanmıştır.

Bu mimari sayesinde:
- Her site için ayrı kod yazmak veya ayrı depolar (repository) yönetmek zorunda kalmazsınız.
- Tek bir güncelleme yaptığınızda tüm siteler otomatik olarak güncellenir.
- Veritabanı ve sunucu bakım maliyetleri minimuma iner.
- Yeni bir site açmak sadece birkaç dakikalık veritabanı kaydı ekleme işlemine dönüşür.

---

## 1. Mimari Yaklaşım Seçimi: Tek Veritabanı (Single-Database Multi-Tenancy)

Çoklu site yapılarında iki temel yaklaşım vardır:
1. **Her siteye ayrı veritabanı (Multi-Database):** Veriler fiziksel olarak ayrıdır.
2. **Tek veritabanında `site_id` ayrımı (Shared-Database):** Tüm sitelerin verisi tek veritabanında tutulur ve sorgularda hangi siteye ait olduğu filtrelenir (Önerilen).

> [!TIP]
> **Öneri:** LikaWeb siteleri kurumsal tanıtım, ürün listeleme ve talep toplama odaklı olduğu için **Tek Veritabanı (Shared-Database)** yaklaşımı en uygun ve yönetimi en kolay olanıdır. Tüm sitelerin tablolarına bir `site_id` kolonu eklenerek veriler mantıksal olarak birbirinden izole edilir.

### İstek ve Veri Akış Şeması

Aşağıdaki şemada, bir kullanıcının siteye girmesinden itibaren verilerin nasıl filtreli bir şekilde sunulduğu gösterilmiştir:

```mermaid
graph TD
    A[Kullanıcı İstek Gönderir] -->|likayazilim.com veya likaone.com| B[Nginx / Apache Sunucu]
    B -->|Tüm domainler aynı Laravel klasörüne yönlenir| C[Laravel Uygulaması]
    C --> D[SiteDomainMiddleware]
    D -->|request->getHost'a göre sorgular| E[Siteler Tablosu]
    E -->|Mevcut Site Bilgisini Belirler| F[Mevcut Site app'current_site' olarak kaydedilir]
    F --> G[Laravel Global Query Scope]
    G -->|Tüm sorgulara otomatik 'where site_id = X' eklenir| H[Veritabanı]
    H -->|Sadece o siteye ait sayfalar, ürünler, ayarlar çekilir| I[Blade Arayüzü & Özelleştirilmiş Tema]
    I --> J[Kullanıcıya Gösterim]
```

---

## 2. Adım Adım Yol Haritası ve Teknik Yapılacaklar

### Adım 2.1: Yeni `siteler` Tablosunun Oluşturulması

İlk olarak, hangi sitelerin sistemde aktif olduğunu tanımlayan bir `siteler` tablosu oluşturulmalıdır.

#### [NEW] `2026_06_10_000000_create_siteler_table.php` (Migration Dosyası)
```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('siteler', function (Blueprint $table) {
            $table->id('site_id');
            $table->string('alan_adi')->unique(); // Örn: likayazilim.com, localhost
            $table->string('baslik');             // Örn: Lika Yazılım A.Ş.
            $table->string('tema')->default('varsayilan'); // Örn: lika, firsat, market
            $table->string('durum')->default('aktif');    // aktif, pasif
            $table->timestamp('olusturulma_tarihi')->nullable();
            $table->timestamp('guncellenme_tarihi')->nullable();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('siteler');
    }
};
```

---

### Adım 2.2: Mevcut Tablolara `site_id` Eklenmesi

Mevcut tablolardaki verilerin hangi siteye ait olduğunu ayırt edebilmek için `site_id` yabancı anahtarı (foreign key) eklenmeli ve benzersiz (unique) kısıtlamalar güncellenmelidir.

#### Güncellenecek Tablolar:
- `yoneticiler` (Yöneticiler her site için ayrı olabileceği gibi, tüm siteleri yöneten Süper Adminler de olabilir)
- `sayfalar` (Sayfaların slug'ları site bazında benzersiz olmalıdır)
- `ana_sayfa_bolumleri` (Her sitenin ana sayfa yapısı farklı olacaktır)
- `urunler` (Her sitenin kendi ürün gamı olacaktır)
- `demo_talepleri` (Hangi siteden demo talebi geldiği ayrıştırılmalıdır)
- `uyelik_talepleri` (Üyelik talepleri siteye göre filtrelenmelidir)
- `ayarlar` (Her sitenin logosu, telefonu, e-postası ve başlığı kendine özeldir)

#### [NEW] `2026_06_10_000001_add_site_id_to_existing_tables.php` (Migration Dosyası)
```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        $tablolar = [
            'yoneticiler',
            'sayfalar',
            'ana_sayfa_bolumleri',
            'urunler',
            'demo_talepleri',
            'uyelik_talepleri',
            'ayarlar'
        ];

        foreach ($tablolar as $tablo) {
            Schema::table($tablo, function (Blueprint $table) use ($tablo) {
                // site_id sütunu ekleme
                $table->foreignId('site_id')
                      ->after($tablo . '_id')
                      ->nullable() // Geriye dönük uyumluluk için ilk başta nullable yapılır
                      ->constrained('siteler', 'site_id')
                      ->onDelete('cascade');
            });
        }

        // Benzersiz (Unique) indexleri site_id ile birleştirerek güncelleme
        Schema::table('sayfalar', function (Blueprint $table) {
            $table->dropUnique(['slug']); // Eski tekli unique kalkar
            $table->unique(['site_id', 'slug']); // Her sitede aynı slug'lı sayfa açılabilir hale gelir
        });

        Schema::table('ayarlar', function (Blueprint $table) {
            $table->dropUnique(['anahtar']);
            $table->unique(['site_id', 'anahtar']);
        });

        Schema::table('ana_sayfa_bolumleri', function (Blueprint $table) {
            $table->dropUnique(['bolum']);
            $table->unique(['site_id', 'bolum']);
        });
    }

    public function down(): void
    {
        Schema::table('sayfalar', function (Blueprint $table) {
            $table->dropUnique(['site_id', 'slug']);
            $table->unique('slug');
        });

        Schema::table('ayarlar', function (Blueprint $table) {
            $table->dropUnique(['site_id', 'anahtar']);
            $table->unique('anahtar');
        });

        Schema::table('ana_sayfa_bolumleri', function (Blueprint $table) {
            $table->dropUnique(['site_id', 'bolum']);
            $table->unique('bolum');
        });

        foreach ([
            'yoneticiler', 'sayfalar', 'ana_sayfa_bolumleri', 
            'urunler', 'demo_talepleri', 'uyelik_talepleri', 'ayarlar'
        ] as $tablo) {
            Schema::table($tablo, function (Blueprint $table) {
                $table->dropForeign(['site_id']);
                $table->dropColumn('site_id');
            });
        }
    }
};
```

---

### Adım 2.3: Domain Tespiti ve Middleware (Site Çözümleme)

Kullanıcı bir istek gönderdiğinde, hangi domain üzerinden geldiğini tespit eden ve bu domain'e ait Site nesnesini Laravel'in hafızasına (Container) alan bir Middleware yazacağız.

#### [NEW] `app/Http/Middleware/SiteMiddleware.php`
```php
<?php

namespace App\Http\Middleware;

use Closure;
use App\Models\Site;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class SiteMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        $host = $request->getHost(); // Örn: 'likayazilim.com' veya 'likaone.com'

        // Domain'e göre siteyi bulalım (Önbellekleme eklenerek veritabanı yükü azaltılır)
        $site = cache()->remember('site_domain_' . $host, 3600, function () use ($host) {
            return Site::where('alan_adi', $host)
                       ->where('durum', 'aktif')
                       ->first();
        });

        // Eğer local geliştirme ortamındaysak ve eşleşen site yoksa, test için ilk siteyi alalım
        if (!$site && app()->environment('local')) {
            $site = Site::where('durum', 'aktif')->first();
        }

        if (!$site) {
            abort(404, 'Bu alan adı için tanımlı bir web sitesi bulunamadı.');
        }

        // Aktif siteyi Laravel Container'a bind edelim (Her yerden erişmek için)
        app()->instance('current_site', $site);

        // İleride config değerlerini de dinamik değiştirebiliriz
        config(['app.name' => $site->baslik]);

        return $next($request);
    }
}
```

> [!NOTE]
> Bu Middleware, `app/Http/Kernel.php` (veya Laravel 11+ kullanılıyorsa `bootstrap/app.php`) içerisindeki **web middleware grubunun en başına** eklenmelidir. Böylece tüm sayfa isteklerinde aktif site tespit edilmiş olur.

---

### Adım 2.4: Trait ve Global Query Scope ile Otomatik Filtreleme

Laravel modellerine tek tek sorgu filtresi (`where('site_id', $site->id)`) eklemek yerine, Eloquent'in **Global Query Scope** özelliğini kullanarak tüm sorguların arka planda otomatik filtrelenmesini sağlayacağız. Böylece mevcut controller kodlarınızda hiçbir değişiklik yapmanız gerekmez.

#### [NEW] `app/Models/Concerns/BelongsToSite.php`
```php
<?php

namespace App\Models\Concerns;

use App\Models\Site;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

trait BelongsToSite
{
    protected static function bootedBelongsToSite(): void
    {
        // 1. Veri Okuma Sorguları (SELECT) için Otomatik Filtreleme
        static::addGlobalScope('site_filter', function (Builder $builder) {
            if (app()->bound('current_site')) {
                $builder->where('site_id', app('current_site')->site_id);
            }
        });

        // 2. Yeni Veri Ekleme (INSERT) aşamasında site_id'yi otomatik yazma
        static::creating(function ($model) {
            if (app()->bound('current_site') && !$model->site_id) {
                $model->site_id = app('current_site')->site_id;
            }
        });
    }

    /**
     * Site İlişkisi
     */
    public function site(): BelongsTo
    {
        return $this->belongsTo(Site::class, 'site_id', 'site_id');
    }
}
```

#### Modellerin Güncellenmesi Örneği (`Sayfa.php`, `Urun.php`, `Ayar.php` vb.)
Tek yapmamız gereken bu trait'i modellere dahil etmektir. Örneğin:

```php
<?php

namespace App\Models;

use App\Models\Concerns\TurkceSoftDeletes;
use App\Models\Concerns\BelongsToSite; // Yeni trait
use Illuminate\Database\Eloquent\Model;

class Sayfa extends Model
{
    use TurkceSoftDeletes;
    use BelongsToSite; // Bu satırı ekliyoruz!

    // ... mevcut kodlar aynen kalır ...
}
```

> [!IMPORTANT]
> Bu trait sayesinde `Sayfa::all()` veya `Urun::where('durum', 'aktif')->get()` sorguları çalıştırıldığında, Laravel arka planda bunu otomatik olarak `SELECT * FROM urunler WHERE site_id = X AND durum = 'aktif'` şekline dönüştürür. Ekstra kod yazmaya gerek kalmaz!

---

## 3. Tema, Tasarım ve Stil Farklılaştırma (Arayüz Yönetimi)

Her sitenin (Örn: Fırsat Haritası ve Market Kurdu) kendine has renkleri, logoları ve hatta bazı özel şablonları olacaktır. Bu farkları kod karmaşası yaratmadan yönetmenin 2 yolu vardır:

### Yaklaşım A: CSS Değişkenleri (Css Variables) ile Renk Yönetimi (Aynı Tasarım, Farklı Renkler)
Eğer sitelerin iskeleti (HTML yapısı) birebir aynı kalıp sadece renkleri, logoları ve fontları değişecekse:
1. `ayarlar` tablosunda `tema_rengi_ana`, `tema_rengi_yardimci` gibi ayarlar tutulur.
2. Ana Blade layout dosyasında (Örn: `app.blade.php`) bu renkler CSS Değişkeni olarak yazdırılır:
   ```html
   <style>
       :root {
           --primary-color: {{ $ayarlar['tema_rengi_ana'] ?? '#3B82F6' }};
           --secondary-color: {{ $ayarlar['tema_rengi_yardimci'] ?? '#1D4ED8' }};
       }
   </style>
   ```
3. CSS dosyalarınızda (CSS/Tailwind) renkler bu değişkenler üzerinden tanımlanır (`bg-[var(--primary-color)]` veya `color: var(--primary-color)`).

### Yaklaşım B: Dinamik Blade Şablonları (Farklı Tasarımlar)
Eğer bazı sitelerin ana sayfaları veya tasarımları tamamen farklı olacaksa, Controller seviyesinde veya bir Custom View Finder ile dinamik blade yüklemesi yapabiliriz:
1. `resources/views/themes/` klasörü oluşturulur.
2. İçine sitenin tema adına göre klasör açılır (Örn: `resources/views/themes/firsat/welcome.blade.php`).
3. Controller içerisinde veya bir helper aracılığıyla tema dosyası kontrol edilir:
   ```php
   $site = app('current_site');
   $tema = $site->tema; // örn: 'firsat'

   // Temaya özel dosya varsa onu yükle, yoksa ana welcome'a düş
   if (view()->exists("themes.{$tema}.welcome")) {
       return view("themes.{$tema}.welcome", $data);
   }

   return view("welcome", $data);
   ```

---

## 4. Medya ve Yüklenen Dosyaların (Logo, Resim vb.) Yönetimi

Çoklu sitelerde resimlerin karışmaması için `storage` dizininde site bazlı klasörleme yapılmalıdır.

- **Mevcut dosya yükleme yapısı:** `public/uploads/resimler/`
- **Yeni çoklu site yapısı:** `public/uploads/site_{site_id}/resimler/`

Bunu otomatikleştirmek için dosya yükleme helper fonksiyonuna `site_id` parametresi eklenmelidir:
```php
public static function dosyaYukle($file, $klasor)
{
    $siteId = app('current_site')->site_id;
    $path = "uploads/site_{$siteId}/{$klasor}";
    
    return $file->store($path, 'public');
}
```

---

## 5. Yönetim Paneli (Admin Panel) Stratejisi

Tüm bu sitelerin içeriklerini nasıl yöneteceğiniz konusunda iki farklı alternatifiniz bulunmaktadır:

| Özellik | Seçenek A: Merkezi Yönetim Paneli (Önerilen) | Seçenek B: Bağımsız Yönetim Panelleri |
| :--- | :--- | :--- |
| **Açıklama** | Tek bir admin panelinden (Örn: `yonetim.likayazilim.com`) üstteki bir açılır menü ile siteler arası geçiş yapılır. | Her sitenin kendi alan adının sonuna `/admin` eklenerek girilir. |
| **Yetkilendirme** | Süper Admin tüm siteleri görür, normal adminler sadece yetkili olduğu siteleri görür. | Adminler sadece giriş yaptıkları domain'e ait verileri düzenleyebilir. |
| **Avantajı** | Tüm ürünleri, sayfaları ve demo taleplerini tek ekrandan izlemek son derece pratiktir. | Sorumluluklar tamamen ayrılır. |
| **Nasıl Yapılır?** | Admin oturumunda seçilen site ID'si Session'da tutulur ve Global Scope bu session'a göre çalışır. | Domain tespiti ile o domain'in site ID'si otomatik olarak scope'a verilir. |

---

## 6. LikaWEB Projesinde Yapılacak Değişiklik Özeti (Kod Farkı)

Mevcut projede yapılacak değişikliklerin özeti şu şekildedir:

### 1. Yeni Dosyalar (NEW)
- [Site.php](file:///c:/laravel/LikaWEB/app/Models/Site.php) Model dosyası.
- [BelongsToSite.php](file:///c:/laravel/LikaWEB/app/Models/Concerns/BelongsToSite.php) Global Scope Trait'i.
- [SiteMiddleware.php](file:///c:/laravel/LikaWEB/app/Http/Middleware/SiteMiddleware.php) İstek algılama katmanı.
- İki adet migration dosyası (`siteler` tablosu ve kolon ekleme migration'ları).

### 2. Değişecek Dosyalar (MODIFY)
- [web.php](file:///c:/laravel/LikaWEB/routes/web.php): Rotaların başına veya Kernel'e Middleware eklenecek.
- Tüm veri modelleri (`Sayfa`, `Ayar`, `Urun`, `AnaSayfaBolumu`, `DemoTalebi`, `UyelikTalebi`): İçlerine `use BelongsToSite;` eklenecek.
- [AnaSayfaController.php](file:///c:/laravel/LikaWEB/app/Http/Controllers/AnaSayfaController.php): Tasarım ihtiyacına göre görünüm çağırma satırı dinamik hale getirilecek.

---

## 7. Önerilen Geçiş Planı (Roadmap)

Eğer bu mimariyi onaylıyorsanız, geçişi şu sıralama ile yapabiliriz:

1. **Adım 1:** Veritabanı Değişiklikleri. Migration dosyalarını hazırlayıp veritabanını güncellemek ve mevcut verilerin `site_id` alanlarını doldurmak (örn: `site_id = 1` olarak Lika Yazılım'a atamak).
2. **Adım 2:** Model & Trait Entegrasyonu. Modellerimize `BelongsToSite` trait'ini eklemek.
3. **Adım 3:** Middleware Tanımlama. Gelen domain'i yakalayan Middleware'i yazmak ve route'ları bu middleware ile sarmalamak.
4. **Adım 4:** Arayüz Özelleştirmeleri. Renk ve tema farklılaştırma yapısını kurmak.
5. **Adım 5:** Test Süreci. Local'de `hosts` dosyasına test domainleri ekleyerek (`likaone.test`, `marketkurdu.test` vb.) geçişin sorunsuz çalıştığını doğrulamak.
