# ASTROBİLGE.COM — KAPSAMLI TEKNİK PROJE DOKÜMANTASYONU

> **Doküman tarihi:** 2026-09-20
> **Kök dizin:** `C:\inetpub\vhosts\astrobilge.com\httpdocs`
> **Kapsam:** Laravel 12 / PHP 8.2 tabanlı çok modüllü astroloji platformu
> **Yöntem:** Bu doküman tahmine değil, doğrudan kaynak kod okumasına ve canlı
> veritabanı sorgularına dayanır. Her iddia için dosya yolu ve (mümkün olduğunda)
> satır numarası verilmiştir. Kesin olmayan yerler açıkça **TAHMİN:** etiketiyle
> işaretlenmiştir.

---

## İÇİNDEKİLER

1. [Genel Mimari](#1-genel-mimari)
2. [Slot Panel Sistemi](#2-slot-panel-sistemi)
3. [Chat (AI Sohbet) Sistemi](#3-chat-ai-sohbet-sistemi)
4. [Kitap Kütüphanesi Sistemi](#4-kitap-kütüphanesi-sistemi)
5. [Yorum (Comment) Sistemi](#5-yorum-comment-sistemi)
6. [Kullanıcı Yönetimi ve Abonelik](#6-kullanıcı-yönetimi-ve-abonelik)
7. [Veritabanı Şeması Özeti](#7-veritabanı-şeması-özeti)
8. [Admin Panel](#8-admin-panel)
9. [Bilinen Sorunlar / Teknik Borç](#9-bilinen-sorunlar--teknik-borç)

---
---

# 1. GENEL MİMARİ

## 1.1. Teknoloji Yığını (Stack)

Kaynak: `composer.json`

| Bileşen | Sürüm / Değer | Not |
|---|---|---|
| PHP | `^8.2` (kurulu: **8.2.33**, NTS VC2019 x64) | `composer.json` → `config.platform.php = 8.2.33`; `php -v` çıktısıyla doğrulandı |
| Laravel Framework | `^12.0` | `laravel/framework` |
| İşletim sistemi | Windows Server 2019 Datacenter (10.0.17763) | IIS + Plesk vhost yapısı (`C:\inetpub\vhosts\...`) |
| Veritabanı | MariaDB 10.11 | `C:\Program Files\MariaDB 10.11\bin\mysql.exe` |
| Uygulama ortamı | `APP_ENV=production`, `APP_URL=https://www.astrobilge.com` | `.env` |

### 1.1.1. Ana Composer Paketleri

`composer.json` → `require` bloğundan birebir:

```
"abraham/twitteroauth": "^8.2"      → X/Twitter OAuth (sosyal medya otomasyon modülü)
"barryvdh/laravel-dompdf": "^3.0"   → PDF üretimi (AI raporları, yorum PDF indirme)
"filament/filament": "^4.0"         → SiteBuilder (astrolog mikro-site) admin paneli
"google/apiclient": "^2.19"         → Google API entegrasyonu
"intervention/image": "^3.11"       → Görsel işleme (avatar, sosyal medya içerikleri)
"laravel/framework": "^12.0"
"laravel/mcp": "^0.7.1"             → Model Context Protocol sunucusu (app/Mcp, 15 dosya)
"laravel/reverb": "*"               → WebSocket (canlı ders / live session)
"laravel/sanctum": "^4.0"           → API token auth (mobil + API platformu)
"laravel/socialite": "^5.24"        → Google & Facebook sosyal giriş
"laravel/tinker": "^2.10.1"
"smalot/pdfparser": "^2.12"         → Kitap arşivinden PDF metin çıkarımı
```

Dev bağımlılıkları: `pestphp/pest ^3.8`, `pestphp/pest-plugin-laravel ^3.2`,
`fakerphp/faker`, `laravel/pail`, `laravel/pint`, `laravel/sail`,
`mockery/mockery`, `nunomaduro/collision`.

### 1.1.2. Autoload Özelliği

`composer.json` → `autoload.files` içinde **`app/Helpers/helpers.php`** global olarak
yüklenir. Bu dosyada panel için kritik olan `panel_cache_version()` ve `asset_v()`
gibi yardımcılar tanımlıdır (bkz. §2.7.1).

---

## 1.2. Veritabanı Bağlantıları

Kaynak: `config/database.php`

`connections` dizisinde **6 bağlantı tanımı** vardır: `sqlite`, `mysql`,
`knowledge`, `mariadb`, `pgsql`, `sqlsrv`. Bunlardan üçü gerçek anlamda
kullanılır/anlamlıdır:

### 1.2.1. `mysql` — Ana (varsayılan) bağlantı

`config/database.php` satır ~45-63:

```php
'mysql' => [
    'driver' => 'mysql',
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '3306'),
    'database' => env('DB_DATABASE', 'laravel'),
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'charset' => env('DB_CHARSET', 'utf8mb4'),
    'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
    'strict' => true,
],
```

`.env` gerçek değerleri:

```
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=astro
DB_USERNAME=astro
```

> **Not:** `config/database.php` içindeki `'default' => env('DB_CONNECTION', 'sqlite')`
> ifadesindeki `sqlite` sadece Laravel iskeletinden gelen varsayılandır; `.env`'de
> `DB_CONNECTION=mysql` olduğu için üretimde **hiçbir zaman devreye girmez**.

### 1.2.2. `knowledge` — Bilgi/vaka bağlantısı

`config/database.php` satır ~65-78:

```php
'knowledge' => [
    'driver' => 'mysql',
    'host' => env('DB_KNOWLEDGE_HOST', env('DB_HOST', '127.0.0.1')),
    'port' => env('DB_KNOWLEDGE_PORT', env('DB_PORT', '3306')),
    'database' => env('DB_KNOWLEDGE_DATABASE', 'astro'),
    'username' => env('DB_KNOWLEDGE_USERNAME', 'astro'),
    'password' => env('DB_KNOWLEDGE_PASSWORD', 'F3314een~'),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'strict' => true,
],
```

**KRİTİK BULGU:** `.env` içinde:

```
DB_KNOWLEDGE_HOST=127.0.0.1
DB_KNOWLEDGE_PORT=3306
DB_KNOWLEDGE_DATABASE=astro      ← ana veritabanıyla AYNI
DB_KNOWLEDGE_USERNAME=astro
```

Yani `knowledge` bağlantısı **fiziksel olarak ayrı bir veritabanına işaret
etmiyor** — `mysql` ile birebir aynı `astro` şemasına bağlanıyor. Bu, **mantıksal
bir ayrım** (logical separation): kod içinde biyografi/vaka tabloları
`DB::connection('knowledge')` üzerinden sorgulanıyor; böylece ileride bu tablolar
ayrı bir sunucuya/replikaya taşınmak istendiğinde **tek satır `.env` değişikliğiyle**
ayrılabilir hale getirilmiş.

Ayrıca bu ayrımın **pratik bir yan faydası** var ve kodda bilinçli olarak
kullanılıyor: `knowledge` bağlantısı ayrı bir PDO oturumu açtığı için, o oturuma
özel `SET SESSION max_statement_time = 3` uygulanabiliyor ve bu ayar ana
uygulamanın sorgularını etkilemiyor (bkz.
`app/Services/Ai/AstrologicalReasoningEngine.php:645-660` ve §9.5).

`knowledge` bağlantısını kullanan yerler (grep ile doğrulandı):

| Dosya | Satır | Kullanım |
|---|---|---|
| `app/Services/Ai/AstrologicalReasoningEngine.php` | ~638-680 | `biography_case_natal_signature_keys` üzerinde açı imzası örtüşme taraması |
| `app/Services/Ai/AstrologyCaseFutureMatchService.php` | ~243, ~270, ~325 | `biography_case_signature_keys` + `biography_case_events` + `biography_cases` join'leri |

### 1.2.3. Diğer bağlantılar

- **`mariadb`** — Laravel 12 iskeletiyle gelen alternatif MariaDB sürücüsü;
  `.env`'de `DB_CONNECTION` bu değere ayarlı değil, **kullanılmıyor**.
- **`pgsql`, `sqlsrv`, `sqlite`** — iskelet artığı, kullanılmıyor.

### 1.2.4. Redis

`config/database.php` → `redis` bloğu tanımlı (`default` + `cache` veritabanları,
`phpredis` client). Ancak `.env`'de:

```
CACHE_STORE=database
SESSION_DRIVER=file
QUEUE_CONNECTION=database
```

Yani **Redis fiilen kullanılmıyor**; cache veritabanı (`cache` tablosu), session
dosya, kuyruk veritabanı (`jobs` tablosu) üzerinden çalışıyor.

> Bu seçimin panel/chat performansına doğrudan etkisi var — `SESSION_DRIVER=file`
> nedeniyle PHP'nin dosya tabanlı oturum kilidi, aynı tarayıcıdan gelen eşzamanlı
> AJAX isteklerini sıraya sokuyor. Bu, slot panelindeki en büyük mimari kısıt
> ve panel.js'teki global fetch kuyruğunun (`queueChartFetch`) varlık sebebi
> (bkz. §2.5.2, `public/frontend/js/panel/panel.js:1721-1745`).

### 1.2.5. Önbellek Katmanları (Cache Stores)

Kodda **iki ayrı cache store** bilinçli olarak kullanılıyor:

| Store | Kullanım | Neden |
|---|---|---|
| `Cache::` (varsayılan = `database`) | Küçük anahtarlar: `cp_version_summary`, `rag:*`, `biography_case_match:*`, `user:{id}:has_active_subscription`, `future_case_match_v3:*` | Küçük payload, TTL kısa |
| `Cache::store('file')` | Panel chart verisi (`panel_chart:*`), panel HTML (`panel_html:*`), lazy yanıt (`lazy_resp:*`), SVG render (`svg_render:*`), tab HTML (`panel_tabs:*`), `panel_build:*` | **MySQL `max_allowed_packet` limiti** |

`IndexController.php:160-164` yorumu birebir:

```
// ✅ Genişletilmiş asteroid (178) + sabit yıldız (286) kataloğuyla panel
// verisi büyüdü; varsayılan 'database' cache store'a (MySQL) yazılırken
// "max_allowed_packet" limitini aşıyor. Bu payload için dosya tabanlı
// cache'e geçiyoruz — MySQL ayarına dokunmadan aynı sonucu verir.
```

---

## 1.3. `bootstrap/app.php` — Uygulama Kompozisyonu

Laravel 12'nin yeni "bootstrap-first" yapısı kullanılıyor. `bootstrap/app.php`
dosyası, klasik `Kernel.php` yerine tüm middleware/exception/route kaydını
tek yerde yapıyor.

### 1.3.1. Route Dosyaları ve Yükleme Sırası

`withRouting(...)` içinde:

```
channels: routes/channels.php     (23 satır)
web:      routes/web.php          (1143 satır)
api:      routes/api.php          (154 satır)
commands: routes/console.php      (27 satır)
health:   /up
```

`then:` closure'ında **sıraya duyarlı** ek kayıtlar yapılıyor:

1. **`routes/apiv1.php`** (164 satır) — `api/v1` prefix, `['api.key', 'throttle:api-platform']`
   middleware'leri. Public Developer API.
2. **`routes/api_panel.php`** (82 satır) — `config('api_platform.panel_path', 'api-panel')`
   prefix, `web` middleware, `apipanel.` name prefix. **Subdomain kullanılmıyor**;
   ana host altında `/api-panel` yolu. Yorumda gerekçesi yazılı: DNS/vhost/SSL
   gerektirmemesi ve `php artisan serve` ile çalışabilmesi.
3. **SiteBuilder domain ayrımı** — `App\Services\SiteBuilder\CentralHost::isSiteBuilderRequest(request())`
   true ise **SADECE** `routes/sitebuilder_public.php` (38 satır) yüklenir ve
   `return` edilir. Böylece ana sistemin CMS catch-all'ı (`/{slug}`) hiç kayıtlı
   olmaz ve iki tarafın aynı slug'ı istemesi çakışmaya yol açmaz.
4. **`routes/site_builder.php`** (72 satır) — `/sitem/*` onboarding hunisi.
5. **En son:** kök seviye CMS catch-all:
   ```php
   Route::get('/{slug}', [\App\Http\Controllers\Frontend\PageController::class, 'show'])
       ->name('page.show');
   ```
   Yorumda net: bu tek segmentli "yakala" route'u **tüm diğer kayıtlardan SONRA**
   tanımlanmalı, yoksa `/api-panel` gibi tek segmentli alt sistemleri gölgeler.

Ek olarak `routes/ai.php` (29 satır) da mevcut — **TAHMİN:** başka bir yerden
(service provider) yükleniyor veya kullanım dışı; `bootstrap/app.php` içinde
referansı yok.

### 1.3.2. Misafir (Guest) Yönlendirmesi

`redirectGuestsTo(...)` closure'ı, path'e göre farklı login sayfalarına yönlendirir:

| Koşul | Yönlendirme |
|---|---|
| `/api-panel*` | `route('apipanel.login')` |
| `/sitem/*` | `route('sitebuilder.auth.login')` |
| `/admin` veya `/admin/*` | `route('admin.login')` |
| diğer | `route('login')` |

### 1.3.3. Giriş Yapmış Kullanıcı Yönlendirmesi

`redirectUsersTo(...)`: `/sitem*` ise `route('sitebuilder.wizard.index')`,
aksi halde framework varsayılanı (`dashboard` → `home` → `/`).

### 1.3.4. Web Middleware Grubuna Eklenenler

```php
$middleware->web(append: [
    \App\Http\Middleware\SecurityHeaders::class,
    \App\Http\Middleware\SetLocale::class,
    \App\Http\Middleware\TrackVisit::class,
]);
```

`TrackVisit` → `visitors` (78.857 satır) ve `page_views` (103.327 satır) tablolarını
besler (bkz. §7).

### 1.3.5. TrimStrings İstisnası

```php
$middleware->trimStrings(except: ['data.sdp']);
```

Gerekçe yorumda: WebRTC SDP metni her satır sonunda `\r\n` ile biter; varsayılan
`TrimStrings` son satırın `\r\n`'ini siliyor ve karşı tarafta
`setRemoteDescription` "Invalid SDP line" hatasıyla patlıyordu.

### 1.3.6. Şifrelenmeyen Cookie'ler

```php
$middleware->encryptCookies(except: [
    'panel_layout_state_relationship',
    'panel_layout_state_dual',
]);
```

Bu iki cookie **slot panel sisteminin ilişki/çoklu harita modundaki düzen
hafızasıdır** ve hem PHP hem JS tarafından okunup yazıldığı için şifrelenmez
(bkz. §2.4.2).

### 1.3.7. Middleware Alias'ları

```php
'admin.auth'                 => \App\Http\Middleware\EnsureAdminIsAuthenticated::class,
'social.profile.complete'    => \App\Http\Middleware\CheckSocialProfileComplete::class,
'require.social.profile'     => \App\Http\Middleware\RequireCompleteSocialProfile::class,
'api.key'                    => \App\Http\Middleware\ApiKeyAuth::class,
'api.meter'                  => \App\Http\Middleware\ApiMeter::class,
'mcp.access'                 => \App\Http\Middleware\McpAccess::class,
'social-automation.api-key'  => \App\Http\Middleware\SocialAutomation\VerifyAutomationApiKey::class,
```

### 1.3.8. CSRF İstisnaları

`validateCsrfTokens(except: [...])` listesi (birebir):

```
logout
auth/facebook/data-deletion
odeme/paytr/bildirim
api/odeme/paytr/bildirim
odeme/paytr/api-bildirim
sitem/odeme/paytr/bildirim
ai-assistant/transcribe
ai-assistant/check-chart-rules
panel/load-chart-lazy
panel/relationship-chart-lazy
panel/chart-combo
panel/transit-refresh
panel/layout-state
report-error
```

Panel AJAX uçları (`panel/load-chart-lazy`, `panel/chart-combo`, `panel/transit-refresh`,
`panel/layout-state`) CSRF'den muaf tutulmuş; yorumda gerekçe "auth middleware ile
korunur" olarak belirtilmiş. `report-error` için gerekçe: bu buton tam olarak
session/CSRF'in bozuk olabileceği hata sayfalarında görünür.

### 1.3.9. Exception Handling

`withExceptions(...)` içinde **ilk sırada** (bilinçli olarak) şu kayıt var:

```php
$exceptions->render(function (\Throwable $e, $request) {
    \App\Services\ErrorLogRecorder::record($e, $request);
    return null;   // yanıt zincirine dokunmaz
});
```

Yorumda gerekçe: `reportable()` yerine `render()` kullanılıyor çünkü Laravel'in
varsayılan `dontReport` listesi `HttpException` türevlerini (404/503 dahil)
`report()`'tan muaf tutuyor; `render()` ise her exception için koşulsuz çalışır.
Bu, admin panelindeki "Hatalar" ekranını (`error_logs`, `error_log_occurrences`
tabloları) besler.

Sonraki `render()` kayıtları:
- `ThrottleRequestsException` → `api/v1/*` ve `mcp` için envelope, diğerleri için
  JSON veya `errors.429` view'ı.
- `ApiPlatformException` → API v1 hata sözleşmesi
- `ValidationException` → API v1 için `VALIDATION_ERROR`
- Genel `Throwable` → API v1 için `CALC_ERROR` (500)

---

## 1.4. `routes/web.php` Genel Yapısı

Toplam **1143 satır**. Ana bölümler:

### 1.4.1. Middleware'siz (Public) Bölüm — satır 1–195

- `GET /test-browser-login` (satır 81) — **TEKNİK BORÇ:** Üretim ortamında duran
  bir geliştirme kısayolu. `murat@visiosoft.com.tr` kullanıcısını **doğrulama
  yapmadan** login eder. Public ve korumasız.
- `GET /lang/{locale}` (satır 92) — dil değiştirme; `config('locales.supported')`
  ile whitelist (`tr`, `en`, `ar`, `de`)
- `GET /geonames/cities` — şehir arama (`MainController::search`)
- `GET /referans-kodu-dogrula` (satır 114) — referans kodu canlı doğrulama, misafir erişimli
- `GET /sitemap.xml`, `GET /feeds/google-merchant.xml`
- **AI Assistant uçları (satır 125-138):**
  ```
  GET  /ai-assistant/strings
  GET  /ai-assistant/context
  GET  /ai-assistant/context-aspects
  POST /ai-assistant/check-chart-rules   [require.social.profile]
  POST /ai-assistant/send-message        [require.social.profile]  ← CHAT ANA UCU
  POST /ai-assistant/transcribe          [require.social.profile]
  POST /ai-assistant/generate-pdf        [auth]
  GET  /ai-assistant/history/sessions    [auth]
  GET  /ai-assistant/history/sessions/{turnUuid} [auth]
  ```
  `require.social.profile` middleware'inin gerekçesi satır 129-132'de yazılı:
  profili eksik (telefon hiç girilmemiş) sosyal kullanıcı, panele hiç girmeden
  bu uçlara doğrudan istek atıp chat'i kullanamasın.
- `GET /panel-preview` (satır 142) — abonelik olmadan panel önizleme; giriş yapılmışsa
  querystring korunarak `/panel`'e redirect
- `GET /panel-preview-form/{mode}` (satır 152) — `whereIn('mode', ['natal','solar_return','secondary_progression_361','secondary_progression_naibod'])`
- `GET /email/verify/{id}/{hash}` (satır 176) — **`auth` grubunun DIŞINDA**, `signed`
  middleware ile. Satır 165-175'te 11 satırlık gerekçe: Laravel'in standart
  `EmailVerificationRequest`'i giriş yapmış kullanıcı ID'sinin URL'deki `{id}` ile
  aynı olmasını şart koşuyor ve farklı cihaz/tarayıcı senaryosunda 403 dönüyordu.

### 1.4.2. `auth` Middleware Grubu — satır 196–399

`Route::middleware(['auth'])->group(...)`. ~200 satır, platformun kalbi:

| Alt grup | Örnek route'lar |
|---|---|
| **Panel** | `/panel` (ayrıca `require.social.profile`), `/panel/csrf-token`, `/complete-profile` |
| **E-posta doğrulama** | `/email/verify` (notice), `/email/verification-notification` (`throttle:1,1`) |
| **Haritalarım** | `/haritalarim` (veya `/my-charts`) CRUD |
| **İlişki haritası** | `/relationship-preview`, `/relationship-charts`, `/relationships/{rel}` |
| **Çoklu harita (dual)** | `/dual-preview`, `/dual-charts` |
| **Panel AJAX (SLOT SİSTEMİ)** | `/panel/transit-refresh`, `/panel/chart-payload`, `/panel/my-charts-modal`, `/panel/chart-tabs`, `/panel/chart-combo`, `/panel/load-chart-lazy`, `/panel/relationship-chart-lazy`, `/panel/layout-state` (`throttle:30,1`) |
| **Rektifikasyon** | `/panel/rectification/{start,wizard,run}` |
| **Elektif** | `/panel/electional/run` |
| **Özel noktalar** | `/panel/custom-points` (GET/POST/DELETE) |
| **Rapor/Yorum** | `/panel/report-pdf`, `/panel/use-comment`, `/panel/generate-natal-report` |
| **Time gate** | `/panel/time-gate-state` (GET + POST, `throttle:30,1`) |
| **Canlı ders** | `/panel/live` ve 11 alt uç (WebRTC signal, ICE, annotation, recording) |
| **Öğretmen paneli** | `/panel/ogretmen` + 4 alt uç |
| **Ders kayıtları** | `/panel/kayitlarim` + 3 alt uç |
| **Abonelik/paket** | `/abone-ol`, `/paket-satin-al`, `/subscription/consume-map-credit` |
| **Sepet & ödeme** | `/sepet/*`, `/odeme/paytr/{basarili,hata}` |
| **Mağaza** | `/adreslerim`, `/siparislerim` |
| **Profil** | `/profilim`, `/bakiye`, `/profile/*`, `/yorumlarim`, `/profile/comments/{comment}/pdf` |
| **Fatura** | `/invoices/{id}/{download,view}` |

**Çift dil URL örüntüsü:** `$isEnRouteLocale` değişkeni (satır 121-122) ile
`APP_LOCALE` `en*` ise İngilizce slug, değilse Türkçe slug kullanılır:

```php
$routeLocale = strtolower((string) env('APP_LOCALE', app()->getLocale()));
$isEnRouteLocale = str_starts_with($routeLocale, 'en');
// ...
Route::get($isEnRouteLocale ? '/my-charts' : '/haritalarim', ...)
```

### 1.4.3. Oturum Gerektirmeyen Ödeme/Hata Uçları — satır 400–436

- `POST /odeme/paytr/bildirim` — PayTR server-to-server callback
- `GET /odeme/mobil/{tamamlandi,hata}` — mobil API ödeme sonucu
- `GET /oturum-sonlandi` (veya `/session-expired`)
- `GET /` — giriş yapılmışsa `/panel`'e redirect, aksi halde `MainController::index()`
- İki faktörlü doğrulama: `/two-factor/verify` (GET+POST), `/two-factor/resend`
  (`throttle:2,1`) — **bilinçli olarak `guest` grubunun dışında**, erişim kontrolü
  controller içinde session bayraklarıyla yapılıyor (satır 421-423 yorumu)
- `Route::match(['get','post'], '/logout', ...)` — "Tüm yazılım için tek çıkış noktası"
- `POST /report-error` (`throttle:20,1`) — hata sayfalarındaki "Bunu bildir" butonu

### 1.4.4. `guest` Middleware Grubu — satır 438–452

```php
Route::middleware(['guest'])->group(function () {
    // GET  /login              AuthController@showLogin
    // POST /login              AuthController@login        [throttle:5,1]
    // GET  /register           AuthController@showRegister
    // POST /register           AuthController@register
    // GET  /forgot-password    PasswordResetController@showForgotPasswordForm
    // POST /forgot-password    PasswordResetController@sendResetLinkEmail  [throttle:3,5]
    // GET  /auth/google        + callback
    // GET  /auth/facebook      + callback
});
```

### 1.4.5. Şifre Sıfırlama — `guest` DIŞINDA (satır 455–464)

**Bu, 2026-09-19'da düzeltilen canlı hatadır** (bkz. §9.4):

```php
// ✅ 2026-09-19 (canlıda acil hata: astrologtilbe@gmail.com şifre sıfırlama
// linkine tıklayınca ana sayfaya atılıyordu): bu iki rota önceden yukarıdaki
// 'guest' grubu İÇİNDEYDİ — 'guest' middleware (RedirectIfAuthenticated),
// tarayıcıda hâlâ AKTİF bir oturum varsa kullanıcıyı controller'a hiç
// ulaştırmadan doğrudan ana sayfaya yönlendiriyordu.
Route::get('/reset-password/{token}', [PasswordResetController::class, 'showResetForm'])->name('password.reset');
Route::post('/reset-password', [PasswordResetController::class, 'reset'])->middleware('throttle:3,5')->name('password.update');
```

### 1.4.6. Public İçerik Sayfaları — satır 466–886

- **Yasal sayfalar:** `/gizlilik-politikasi`, `/hizmet-sartlari`, `/nasil-calisir`,
  `/hakkimizda`, `/kvkk`, `/teslimat-sartlari`, `/iade-politikasi`,
  `/cerez-politikasi`, `/mesafeli-satis-sozlesmesi`, `/hesap-silme`
- **Facebook veri silme callback:** `/auth/facebook/data-deletion` (GET+POST)
- **Blog:** `/blog`, `/blog/{blog:slug}`
- **Mağaza:** `/magaza`, `/magaza/kategori/{category:slug}`, `/magaza/{product:slug}`
  — yorumda açıkça: "ticari akış, `FRONTEND_CONTENT_PAGES_ENABLED=false` olsa da
  açık kalmalı"
- **API Servisleri (public tanıtım/satış):** `/api-servisleri`,
  `/api-servisleri/fiyatlandirma`, `/api-servisleri/mcp`,
  `/api-servisleri/dokumantasyon/{page?}`
- **SEO içerik sayfaları:** doğum haritası, gezegenler, burçlar, elementler —
  her biri için **eski URL → yeni URL redirect** çiftleri (satır 535-793 arası,
  ~25 adet redirect kaydı)

### 1.4.7. Admin Paneli — satır 887–1143

Bkz. §8.

---

## 1.5. `app/` Klasör Yapısı ve Dosya Sayıları

Gerçek sayım (`find app/X -name "*.php" | wc -l`):

| Dizin | PHP dosya sayısı | Amaç |
|---|---:|---|
| `app/Services/` | **181** | İş mantığının tamamı; en büyük katman |
| `app/Http/` | **155** | Controller (97) + Middleware (13) + Requests/Responses/Resources |
| `app/Models/` | **98** | Eloquent modelleri |
| `app/Filament/` | **39** | SiteBuilder (astrolog mikro-site) yönetim paneli kaynakları |
| `app/Console/` | **34** | Artisan komutları (31 komut + diğer) |
| `app/Mcp/` | **15** | Model Context Protocol sunucusu / araçları |
| `app/Notifications/` | **7** | E-posta/SMS bildirimleri (VerifyEmail, ResetPassword vb.) |
| `app/Jobs/` | **5** | `WarmFutureCaseMatchJob`, `ProvisionAstrologerSite` + SocialAutomation |
| `app/Providers/` | **5** | Service provider'lar |
| `app/View/` | **3** | Blade bileşenleri (`UserAvatar` vb.) |
| `app/Helpers/` | **3** | `helpers.php` (global fonksiyonlar), `UserHelper` vb. |
| `app/Contracts/` | **2** | Arayüzler |
| `app/Listeners/` | **2** | Event dinleyicileri |
| `app/Events/` | **1** | |
| `app/Rules/` | **1** | `ValidReferralCode` |

### 1.5.1. Controller Dağılımı

| Dizin | Dosya | Amaç |
|---|---:|---|
| `app/Http/Controllers/Admin/` | **42** | Admin paneli (blog, paket, kupon, mağaza, analytics, API platformu, sosyal otomasyon…) |
| `app/Http/Controllers/Frontend/` | **31** | Panel, auth, profil, sepet, mağaza, blog, rektifikasyon, elektif, canlı ders |
| `app/Http/Controllers/Api/` | **9** | Mobil/dahili API |
| `app/Http/Controllers/ApiV1/` | **9** | Public Developer API v1 |
| `app/Http/Controllers/SiteBuilder/` | **9** | Astrolog mikro-site public + wizard |
| `app/Http/Controllers/ApiPanel/` | **7** | API müşteri paneli |

**En büyük controller:** `app/Http/Controllers/Frontend/IndexController.php` —
**4.816 satır**, 50 metot. Panel sayfasının tamamı buradan yönetilir (bkz. §2).

### 1.5.2. Services Katmanı — Alt Dizinler

```
app/Services/
├── Admin/              → RegistrationLogScanner
├── Ai/                 → 21 dosya — CHAT + VAKA + KÜTÜPHANE MOTORU (bkz. §3, §4)
├── AiAsistan/          → 4 dosya — prompt şablonları, UI stringleri, sayfa bağlamı
├── ApiPlatform/        → API platformu (plan, kredi, ölçüm)
├── Astrology/          → 8 dosya — ArabicParts, Hyleg, Cosmobiology, Profection,
│                          SolarFireAspectPattern, CrossAspectKeyCalculator,
│                          MonthlyPredictiveTiming, AstrologicalBodyList
├── Auth/               → LogoutService, SingleSessionService, SocialAuthService,
│                          SocialUserDto, TwoFactorService
├── Chart/              → ChartPageBuilderService (2176), ChartRequestResolverService,
│                          PlanetCatalog, CriticalDegreeService, ChartOwnerNameResolver
├── DualChart/          → Çoklu harita servisi
├── OpenAI/             → OpenAIServiceRefactored (83 KB), PromptSelectorService,
│   ├── Mappers/            RulershipMapper
│   ├── Prompts/            21 prompt sınıfı + SystemPromptBuilder
│   └── Transformers/       ChartDataTransformer (2178), Planet/House/AspectTransformer
├── Profile/            → ProfileService
├── SiteBuilder/        → Astrolog mikro-site (availability, provisioning, CentralHost)
├── Sms/                → SMS sağlayıcı entegrasyonu
└── SocialAutomation/   → Sosyal medya otomasyon modülü
```

**Kök seviye servisler (en büyükler):**

| Dosya | Boyut | Amaç |
|---|---:|---|
| `ChartVisualizerService.php` | **316 KB / 7.260 satır** | Harita görselleştirme — SVG üretimi, tablo hazırlama |
| `SwetestService.php` | **146 KB** | Swiss Ephemeris binary çağrıları (efemeris hesabı) |
| `HouseLordsService.php` | 46 KB | Ev yöneticileri analizi |
| `RectificationService.php` | 46 KB | Doğum saati rektifikasyonu |
| `ElectionalService.php` | 31 KB | Elektif astroloji (uygun zaman seçimi) |
| `PackageService.php` | 31 KB | Paket/token satın alma ve tüketimi |
| `RelationshipChartService.php` | 31 KB | Sinastri/kompozit/davison |
| `LunarPhasesService.php` | 29 KB | Yeniay/dolunay/tutulma |
| `VectorSearchService.php` | 28 KB | RAG semantik arama |
| `DominantPlanetService.php` | 22 KB | Baskın gezegen hesabı |
| `SwissEphemerisFfiService.php` | 19 KB | **FFI** ile Swiss Ephemeris (process-spawn darboğazı çözümü, commit `8975aa36`) |
| `SubscriptionService.php` | 12 KB | Abonelik planları ve kontrolleri |
| `ReferralCodeService.php` | 9.6 KB | Referans kodu |
| `GiftGrantService.php` | 8.6 KB | Hediye kredisi |

### 1.5.3. `app/Console/Commands/` — 31 Komut

**Biyografi/Vaka arşivi (10):**
```
ImportBiographyCasesCommand
ImportHistoricalArchivesCommand
BackfillBiographyCaseChartPositionsCommand   → chart_positions + aspect_keys üretir
BackfillBiographyCaseCountryCommand
BackfillNatalExtendedDataCommand
ClassifyBiographyCaseQualityCommand          → ABQ (Astro Bilge Quality) skoru
ComputeBiographyCaseSignaturesCommand        → olay anı açı imzaları
LinkBiographyCaseRelationshipsCommand        → eş/akraba ilişkileri
FlattenBiographyCaseNatalSignatureKeysCommand → JSON → indeksli düz tablo
FlattenBiographyCaseSignatureKeysCommand
```

**Kitap kütüphanesi (9):**
```
IngestArchiveBooksCommand        → astro:ingest-archive (ana motor, 518 satır)
IngestAllAstrologyBooksCommand
ImportAstrologyBooksCommand
CleanDuplicateBooksCommand
PurgeJunkBookChaptersCommand
RepairFlatDocChaptersCommand     ┐ 2026-09-19'da eklenen 4 "derin kazıma"
RepairFlatDocxChaptersCommand    │ onarım komutu (yanlış bölünmüş kitapları
RepairFlatEpubChaptersCommand    │ yeni motorla yeniden böler)
RepairFlatPdfChaptersCommand     ┘
```

**Harici yazılım içe aktarma (4):**
```
ImportSiriusChartsCommand / ImportSiriusInterpretationsCommand
ImportSolarFireChartsCommand / ImportSolarFireInterpretationsCommand
```

**Diğer:**
```
CrawlAstrologyWikiCommand     → astrology_wiki_articles (1.690 kayıt)
AiPipelineTestCommand         → AI hattı testi
TestReasoningQualityCommand   → muhakeme motoru kalite testi
AddSubscriptionCommand, AdminSetPasswordCommand, ClearAllCaches
GenerateApiDocsExamples, PruneApiUsageLogs, SendEmailVerificationRemindersCommand
SmsTestCommand
app/Console/Commands/SocialAutomation/   (alt dizin)
```

### 1.5.4. `app/Jobs/` — 5 Dosya

| Job | ShouldQueue? | Not |
|---|---|---|
| `WarmFutureCaseMatchJob` | **HAYIR (bilinçli)** | `dispatch(...)->afterResponse()` ile çağrılır. Yorumda gerekçe: *"Bu ortamda kalıcı bir `queue:work` süreci çalışmadığı (jobs tablosunda aylardır işlenmemiş 6.553 iş bulgusuyla tespit edildi, temizlendi) için normal kuyruğa atılan bir iş asla çalışmazdı."* |
| `ProvisionAstrologerSite` | — | SiteBuilder domain/Cloudflare provisioning |
| `app/Jobs/SocialAutomation/*` | — | Sosyal medya içerik işleri |

---

## 1.6. Kimlik Doğrulama Guard'ları

`config/auth.php` → **4 ayrı guard**, hepsi `session` sürücüsü:

| Guard | Provider | Model | Kullanım |
|---|---|---|---|
| `web` (varsayılan) | `users` | `App\Models\User` | Ana site üyeleri |
| `admin` | `admins` | `App\Models\Admin` | Admin paneli |
| `api_customer` | `api_customers` | `App\Models\ApiCustomer` | API platformu müşterileri (ana site üyeliğinden bağımsız) |
| `site_owner` | `site_owners` | `App\Models\SiteBuilder\SiteOwner` | Astrolog mikro-site sahipleri (ana site üyeliğinden tamamen bağımsız) |

Bu, platformun **dört ayrı kullanıcı evreni** olduğu anlamına gelir. `users`
tablosu merkezi "çatı kullanıcı" rolünü üstlenir ve `User` modelinde
`apiCustomer(): HasOne` ile `siteOwner(): HasOne` ilişkileriyle diğer evrenlere
köprü kurar (`app/Models/User.php` → `apiCustomer()` ve `siteOwner()` metotları).

---

## 1.7. Astroloji Hesaplama Altyapısı

### 1.7.1. `SwetestService` (146 KB)

`app/Services/SwetestService.php:15-23`:

```php
public function __construct()
{
    $this->ephePath = storage_path('app/sweph/');
    $this->binPath = $this->ephePath . (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? 'swetest.exe' : 'swetest');
    if (!file_exists($this->binPath)) {
        throw new \Exception('Swetest dosyası bulunamadı: ' . $this->binPath);
    }
}
```

**Dikkat çekici tasarım:** `getBinPath(string $type = 'main')` metodu (satır 29),
harita türüne göre **ayrı binary dosyaları** döner (sadece Windows'ta):

```php
$fileName = match ($type) {
    'transit'                               => 'swetest-transit.exe',
    'solar_arc'                             => 'swetest-solar-arc.exe',
    'secondary', 'secondry', 'solar_return' => 'swetest-secondry.exe',
    // ...
};
```

**TAHMİN:** Bu, Windows'ta aynı EXE'ye eşzamanlı çok sayıda process-spawn
yapıldığında oluşan dosya kilidi/handle darboğazını dağıtmak için yapılmış bir
optimizasyon olabilir. Kodda açık bir gerekçe yorumu yok.

### 1.7.2. `SwissEphemerisFfiService` (19 KB)

Commit `8975aa36`: *"perf(astro): Swiss Ephemeris hesaplamalarini FFI'a tasi
(process-spawn darbogazi cozumu)"*. Yani process başlatma yükü, PHP FFI ile
doğrudan kütüphane çağrısına taşınmış.

### 1.7.3. `ChartRequestResolverService`

`app/Services/Chart/ChartRequestResolverService.php` — `resolveForPage(Request, ?User)`
metodu (satır 14) panel isteğindeki ham query parametrelerini
(`date/time/lat/lon/city/timezone`, `rel_*`, `rel2_*`, `rel3_*`) normalize edip
UTC anlara ve bağlam dizisine çevirir. `resolveExtraPerson(Request, string $prefix, string $defaultTitle)`
(satır 139) 2., 3. ve 4. kişileri çözer.

Bu servis, tüm panel akışının **tek giriş noktasıdır**: `ChartPageBuilderService::build()`
ilk satırda `$ctx = $this->resolver->resolveForPage($request, $user);` çağırır
(`app/Services/Chart/ChartPageBuilderService.php:39`).

---

# 2. SLOT PANEL SİSTEMİ (ÖNCELİKLİ DERİN İNCELEME)

## 2.1. "Slot" Kavramı Nedir?

Panel, aynı anda **5 ayrı harita gösterme alanı** sunar — bunlara "slot" denir:

| Slot adı | Konum | Adet |
|---|---|---|
| `main` | Orta/büyük merkez alan | 1 |
| `left_0`, `left_1` | Sol taraf, üst-alt | 2 |
| `right_0`, `right_1` | Sağ taraf, üst-alt | 2 |

Her slot, bir "chart_type" (harita türü) anahtarı taşır — ör. `natal`, `natal_transit`,
`solar_return`, `solar_arc`, `profection`, `lunar_phases`, `uranian`, `synastry` vb.
Kullanıcı istediği slota istediği harita türünü atayabilir; böylece ör. ortada natal
haritasını, solda transit ve profeksiyon haritalarını, sağda solar return ve
sinastri haritasını AYNI ANDA açık tutabilir.

## 2.2. Veritabanı: `user_panel_layouts` + `user_panel_slot_changes`

- **`user_panel_layouts`** — kullanıcı başına **TEK satır** tutar (`app/Http/Controllers/Frontend/IndexController.php:959` `persistUserPanelLayoutState`
  metodu her zaman önce eski satırı UPDATE eder, varsa DUPLİKE satırları DELETE eder).
  `layout` sütunu JSON: `{main, left:[2], right:[2], ayanamsa, sidereal_dasha, harmonic}`.
- **`user_panel_slot_changes`** — HER slot değişikliğini AYRI bir satır olarak loglar
  (`UserPanelSlotChange` modeli, satır 8-14'teki yoruma göre: "Admin > Analytics >
  Kullanıcı Detayı sayfasında kullanıcının hangi slotu ne zaman değiştirdiğini, ve
  site genelinde hangi harita tipinin hangi slotta en çok kullanıldığını raporlamak
  için var"). `logPanelSlotChanges()` (satır 1011) SADECE gerçekten değişen slotları
  yazar — değişmeyenler için satır açılmaz.

## 2.3. Yazma Akışı: `POST /panel/layout-state` → `updatePanelLayoutState()`

`IndexController.php:776`. Request `main` (string), `left`/`right` (2 elemanlı
dizi), `ayanamsa`/`sidereal_dasha`/`harmonic` (opsiyonel sayısal ayarlar) alır,
normalize eder, `persistUserPanelLayoutState()` ile DB'ye yazar (transaction
içinde: önce `logPanelSlotChanges` ile değişim logu, sonra UPDATE/INSERT).

## 2.4. Okuma Akışı ve GERÇEK BİR GEÇMİŞ BUG: `solar_arc` Anahtar Anlamı Değişikliği

`getUserPanelLayoutState()` (satır 840) DB'den okurken **`LEGACY_CHART_KEY_MAP`**
(satır 769) ile eski kayıtları çevirir:

```php
private const LEGACY_CHART_KEY_MAP = [
    'solar_arc' => 'solar_arc_biwheel',
    'solar_arc_single' => 'solar_arc',
    'solar_arc_transit' => 'solar_arc_biwheel_transit',
    'solar_arc_single_transit' => 'solar_arc_transit',
];
```

**Hikaye (kod yorumlarından, satır 795-802 ve 866-875):** Bir noktada `solar_arc`
anahtarının anlamı değişti — eskiden "biwheel" (çift çark) tekniğini temsil
ederken, yeni sistemde "tekli teknik" anlamına gelmeye başladı. Bu **GERİYE
DÖNÜK UYUMSUZ** bir rename. Geliştirici önce hem YAZMA hem OKUMA tarafında
string-eşleme ile çözmeye çalışmış — ama bu, YENİ istemcilerin kasıtlı olarak
gönderdiği `solar_arc` değerinin de sessizce `solar_arc_biwheel`'e çevrilmesine,
yani **yeni tekli tipin hiçbir zaman kaydedilememesine** yol açan canlı bir
bug'a neden olmuş. Çözüm: çeviri SADECE okuma tarafında, VE sadece
**`$legacyRenameCutoff = '2026-09-14 13:01:00'`** tarihinden ÖNCE güncellenmiş
satırlara uygulanıyor — bu tarihten sonraki her satır zaten yeni kod tarafından
yazılmış kabul edilip dokunulmuyor. Bu, "veri satırının yaşına göre davranış
değiştirme" (age-based migration) desenine iyi bir canlı örnek.

## 2.5. Varsayılan Düzen: `buildDefaultPanelLayoutState()`

Yeni kullanıcı/hiç kayıt yoksa (satır 1045), `main` = kullanıcının kendi
natal haritası anahtarı (`defaultKey`), `left`/`right` çağıran taraftan
(`leftKeys`/`rightKeys`) gelen varsayılan dizilimle doldurulur.

## 2.6. Panel Sayfası İlk Yükleme: `IndexController::index()` → satır ~438

`$savedPanelLayout = $this->getUserPanelLayoutState(Auth::id(), $defaultPanelLayout, true);`
— `createIfMissing=true` ile çağrılır, yani kullanıcının hiç kaydı yoksa bu anda
otomatik oluşturulur. Bu, sayfa render edilirken PHP tarafında hazırlanan
`$data` dizisine girer ve Blade view'a (`panel.blade.php`) geçirilir; JS
tarafında her slotun hangi harita anahtarıyla başlayacağını belirler.

## 2.7. Sunucu Tarafı Harita Hesaplama Zinciri (Bir Slot Doldurulduğunda)

1. **`ChartRequestResolverService::resolveForPage()`** — ham query/POST
   parametrelerini (`date/time/lat/lon/city/timezone`, `rel_*` ikinci kişi
   parametreleri) normalize edip UTC anlara çevirir (bkz. Bölüm 1.7.3).
2. **`ChartPageBuilderService::build()`** (`app/Services/Chart/ChartPageBuilderService.php:39`)
   — bu servis TÜM panel akışının tek giriş noktası; resolver'ı çağırıp
   ardından ilgili harita türüne göre hesaplama servisine yönlendirir.
3. **`SwetestService`** (146 KB, bkz. Bölüm 1.7.1) veya **`SwissEphemerisFfiService`**
   (FFI ile process-spawn'sız hesaplama, commit `8975aa36`) — gerçek astrolojik
   hesaplamayı (gezegen konumları, evler, açılar) üretir. `SwetestService::getBinPath(string $type)`
   harita türüne göre AYRI binary dosyaları (`swetest-transit.exe`,
   `swetest-solar-arc.exe`, `swetest-secondry.exe` vb.) çağırır — TAHMİN: Windows'ta
   aynı EXE'ye eşzamanlı çok sayıda process-spawn yapıldığında oluşan dosya
   kilidi darboğazını dağıtmak için.
4. **`ChartVisualizerService`** — ham astrolojik veriyi görsel/JSON forma
   (gezegen tablosu, ev tablosu, açı grid'i, sabit yıldız kavuşumları vb.) dönüştürür.
5. Sonuç, ilgili slotun DOM elemanına (SVG + tablo) enjekte edilir.

## 2.8. İstemci Tarafı Yükleme Sırası ve GERÇEK BİR EŞZAMANLILIK BUG'I

`public/frontend/js/panel/panel.js` üç AYRI kaynaktan harita çekme isteği
tetikler:
1. Ertelenen ANA harita yükü (master slotta `natal_transit` gibi bir şey varsa)
2. Sıralı yan-slot hidrasyonu (`hydrateOccupiedSideSlotsSequentially`)
3. **`BACKGROUND_CHART_QUEUE = ['natal_transit', 'profection', 'lunar_phases', 'solar_return']`**
   — sayfa açıldıktan **7 saniye sonra** başlayan, bu 4 harita türünü SIRAYLA
   arka planda önceden hesaplatıp önbelleğe alan mekanizma (`initBackgroundChartQueue()`,
   satır 1888).

**Bulunan kök neden (satır 1722-1732'deki geniş yorum):** PHP'nin varsayılan
dosya-tabanlı oturum kilidi (`SESSION_DRIVER=file`) TÜM isteklerde AYNI
(PHPSESSID bazlı) kilide çarpıyor. Sayfa ilk açıldığında bu ÜÇ kaynak
birbirinden habersiz, birbirini beklemeden AYNI ANDA sunucuya istek atarsa,
PHP'nin oturum kilidi birini bekletip zaman aşımına uğratıyor ("Failed to
fetch") — sonuçta ilgili slot sonsuza kadar iskelet (skeleton) durumunda
takılı kalıyor YA DA (ana slot için) sessizce natal gibi bir yedeğe düşülüyor.

**Çözüm:** Tek bir GLOBAL Promise zinciri (`chartFetchQueueTail` / `queueChartFetch()`,
satır 1731-1737) — TÜM harita-çekme istekleri (üçü de, hatta 2 saniyelik
tam-genişletilmiş-asteroid yükleme döngüsü de `window.queueChartFetch` ile
dışa açılarak) bu TEK sıraya zincirlenir. Böylece bu kaynaklardan asla ikisi
aynı anda ağa çıkmaz — yarış durumu yerine kesin, deterministik sıralama
sağlanır. Bu, "aynı PHP oturum kilidine çarpan çoklu eşzamanlı istek" sınıfı
sorunlar için iyi bir çözüm örneği.

## 2.9. İlgili Diğer Slot/Panel Dosyaları (kısa özet, detaya girilmedi)

- `resources/views/frontend/panel/chart-skeleton.blade.php` — bir slot henüz
  yüklenmemişken gösterilen iskelet/placeholder şablonu
- `public/frontend/js/panel/live-video-slot.js`, `live-webrtc.js` — TAHMİN:
  canlı görüşme/seans özelliği için AYRI bir "slot" kavramı (randevu/booking
  ile ilgili, harita slotlarıyla karıştırılmamalı — muhtemelen `app/Services/SiteBuilder/AvailabilityService.php`
  ile ilişkili randevu zaman dilimi "slot"u)
- `app/Http/Controllers/Admin/AnalyticsController.php` — `user_panel_slot_changes`
  tablosunu kullanıcı detay sayfasında ve site-geneli "en çok kullanılan harita
  tipi" raporunda gösterir (TAHMİN, doğrulanmadı — zaman kısıtı nedeniyle
  controller kodu tam okunmadı)

---

# 3. CHAT (AI SOHBET) SİSTEMİ

Bu bölüm, bu proje üzerinde en son yapılan yoğun hata ayıklama/geliştirme
oturumunun (2026-09-19/20) doğrudan sonucu olarak GERÇEK canlı testlerle
doğrulanmış bilgi içerir.

## 3.1. Uç Nokta ve Giriş Noktası

`POST /ai-assistant/send-message` → `AIAssistantController::sendMessage()`
(`app/Http/Controllers/Frontend/AIAssistantController.php:45`) → tek satırda
devrediyor: `return $this->ai->sendMessage($request);` — gerçek mantığın TAMAMI
`app/Services/Ai/AiService.php::sendMessage()` (satır 422) içinde.

Rota `require.social.profile` middleware'iyle korunuyor (bkz. Bölüm 1.3.7) —
DİKKAT: bu middleware, giriş yapmamış (`$user===null`) istekleri REDDETMEZ,
sadece eksik profilli SOSYAL giriş kullanıcılarını engeller (bkz. Bölüm 9'daki
bulgu). Yanıt **SSE (Server-Sent Events) streaming** formatında döner
(`Content-Type: text/event-stream`, her parça `data: {"chunk":"..."}\n\n`).

## 3.2. `sendMessage()` — Uçtan Uca Akış (satır 422-1900 civarı, ~1500 satır)

1. **Doğrulama** — `message` (opsiyonel, `prompt_type` ile birlikte de gelebilir),
   `chart_key`, `chart_data`, `known_chart_contexts` (aynı anda açık diğer
   slotların özet bağlamı — bkz. Bölüm 2), `conversation_history` YOK (DB'den
   `session_uuid` ile çekiliyor).
2. **Genel-analiz anahtar kelime tespiti** (satır ~1030-1057) — "haritamı
   yorumla" gibi ~15 sabit ifade + bir regex, eşleşirse `$promptType='general'`
   olur ve **`$message` DEĞİŞTİRİLİR**: `AiAsistanPromptTemplates::getPromptForType()`
   çıktısı (şablon metni + TAM harita JSON'u) `$message`'ın yerini alır.
   ⚠️ **Bu davranış canlıda gerçek bir bug'a neden olmuştu** (bkz. Bölüm 9.3) —
   bu yüzden `$userRawMessage` (satır 1084, HER ZAMAN orijinal ham metni tutar)
   ayrıca saklanır ve mesajın GERÇEK metnine bakması gereken tüm alt sistemler
   (ör. ilişki-modalı tespiti) artık bunu kullanır, `$message`'ı değil.
3. **`detectRelationshipChartNeededHint($userRawMessage, ...)`** (satır 1375,
   fonksiyon tanımı 5843) — kullanıcı tekil (natal) haritadayken mesajında
   BAŞKA bir tarih yazıp ilişki yorumu isterse, LLM'e "tek taraflı yorum yapma,
   İlişki Haritası modalını öner" talimatı enjekte eder VE frontend'e
   `open_relationship_modal:true` sinyali gönderir (bkz. Bölüm 3.6).
4. **`AstrologicalReasoningEngine::buildReasoningDossier()`** (satır ~1506) —
   bkz. Bölüm 3.3.
5. **`ChartSlicer::slice()`** — soruya göre harita verisini daraltır (LLM'e
   sadece ilgili gezegen/ev/açı gönderilir, token tasarrufu).
6. **`AstrologySynthesizer::buildSynthesisDirective()`** — çoklu-ajan sentez
   yönergesi.
7. **`AstrologyLibraryService::retrieveDossier()`** (satır ~1512) — bkz. Bölüm 3.4.
8. **Ay Evresi (Lunar Phase) önceliklendirmesi** — SADECE `lunar_phases`/`pre_natal`
   haritalarında devreye girer, varsa TÜM diğer direktiflerin ÖNÜNE eklenir.
9. **Master Harita Önceliği direktifi** — kullanıcı panelde `natal` dışında bir
   "master" harita açıksa (bkz. Bölüm 2), LLM'e "bu harita türünün KENDİ
   kurallarına göre yorumla, standart natal analiz YAPMA" zorunlu talimatı.
10. **Sistem prompt'u birleştirme** → **`OpenAIService::sendMessageStream()`**
    ile OpenAI'ye streaming çağrı (`AiService.php:1651`) — chunk'lar geldikçe
    `echo 'data: {"chunk":...}'; @flush();` ile tarayıcıya akıtılır.
11. **Kayıt** — `AiConversation` tablosuna user+assistant mesajları yazılır
    (`turn_uuid`, `session_uuid`, `conversation_session_uuid` üç ayrı UUID
    kullanılıyor — session_uuid HER MESAJ TURUNDA yeni üretiliyor, sürekliliği
    sağlayan `conversation_session_uuid` istemciden geliyor).
12. **Token/kelime tüketimi** — `PackageService::consumeChatWords($user, $wordCount)`.

## 3.3. `AstrologicalReasoningEngine::buildReasoningDossier()` — "Ajan 1: Mantık Yürütme"

`app/Services/Ai/AstrologicalReasoningEngine.php:58`. İçinde:

- **Kritik gösterge taraması** (satır 91-180 civarı) — sırayla: açı kalıpları
  (T-Kare, Büyük Üçgen vb.) → sabit yıldız kavuşumları → sabit kritik gezegen
  çiftleri (Mars-Satürn, Güneş-Plüton vb., 7° orb) → kritik asteroidler
  (Chiron/Juno/Vesta). İlk eşleşen kazanır.
- **`findEmpiricalCase()`** (satır 283) → **`findBestMatchingCase()`** (satır 541)
  → **gerçek tarihi/ünlü vaka eşleştirmesi**: haritanın burç/açı "imzası"
  (`buildCurrentAspectKeys`) 120.312 vakalık, 12.595.538 satırlık indeksli
  `biography_case_natal_signature_keys` tablosunda aranır. Uranüs/Neptün/Plüton
  gibi çok yavaş hareket eden (=ayırt edici olmayan) noktalar İLK taramaya
  dahil edilmez, sadece ≤50 adaylık ön havuz bulunduktan SONRA ek puan olarak
  ölçülür (performans optimizasyonu). Sonuç 6 saat önbelleğe alınır. **Bu
  sorguya 2026-09-20'de 3 saniyelik `SET SESSION max_statement_time` koruması
  eklendi** (bkz. Bölüm 9.5) — öncesinde 100+ saniye sürüp chat'i kilitleyebiliyordu.
- **`buildFutureCaseMatchBlock()`** (satır 997) → **`AstrologyCaseFutureMatchService::scanFutureWindowCached()`**
  — kullanıcının natal derecelerini gelecek 24 ay boyunca (transit/solar-arc/
  solar-return) tarar, `biography_case_signature_keys` (20.610.676 satır) ile
  eşleştirip "önümüzdeki X ayda benzer haritalarda en sık görülen olay Y"
  tarzı bir blok üretir. **`WarmFutureCaseMatchJob`** ile panel yüklenirken
  ÖNCEDEN ısıtılır — bu iş BİLİNÇLİ OLARAK `ShouldQueue` UYGULAMAZ (kalıcı bir
  `queue:work` süreci bu ortamda çalışmadığı, jobs tablosunda binlerce işlenmemiş
  iş bulunduğu tespit edildiği için — bkz. Bölüm 9.6), bunun yerine
  `dispatch(...)->afterResponse()` ile HTTP yanıtı gönderildikten hemen sonra
  AYNI PHP sürecinde, worker'a ihtiyaç duymadan çalışır.

## 3.4. `AstrologyLibraryService` — Kütüphane Referans Sistemi

`app/Services/Ai/AstrologyLibraryService.php`. **İKİ AYRI veri kaynağı var,
KARIŞTIRILMAMALI:**

1. **`astrology_interpretations_library`** (8.735 kayıt) — `lookupPlanetSign()`,
   `lookupPlanetHouse()`, `lookupAspect()` ile sorgulanan, ÖNCEDEN YAZILMIŞ kısa
   gezegen-burç/gezegen-ev/açı tanımları. `retrieveDossier()` (satır 82) bunları
   haritanın gezegen/açı listesine göre tarar, en fazla 4 tanesini seçer.
2. **`astrology_book_library`** (7.123 kitap, 435M kelime) — `searchBooks()`
   (satır 341) ile FULLTEXT arama yapılan büyük kitap arşivi. **2026-09-20'ye
   KADAR bu fonksiyon hiçbir yerden çağrılmıyordu** (ölü kod) — o gün
   `retrieveDossier()`'a bağlandı: `TOPIC_BOOK_SEARCH_TERMS` haritası (satır
   ~210) konu sınıflandırmasını (`love_relationships`, `career_money` vb.)
   Türkçe arama terimine çevirir, `searchBooks($term, null, 1, 'tr')` ile
   EN ALAKALI TEK kitap alıntısı (700 karaktere kısaltılmış) bulunur. Herhangi
   bir hata/yavaşlık durumunda try/catch ile SESSİZCE atlanır — chat akışı
   ASLA bundan etkilenmez. Detaylar Bölüm 9.7'de.

`detectDirectDefinitionalQuery()` (satır 180) — kullanıcı doğrudan bir tanım
sorusu sorarsa (ör. "Merkür retrosu nedir"), GPT'ye HİÇ gitmeden anında
(fast-path) yanıt üretir — sıfır AI maliyeti.

## 3.5. Konuşma Geçmişi ve UUID Katmanları

`ai_conversations` tablosu üç ayrı UUID sütunu taşır:
- **`turn_uuid`** — HER mesaj-yanıt çiftine özel, benzersiz.
- **`session_uuid`** — DB satırlarını gruplamak için kullanılan, HER YENİ
  mesaj turunda YENİDEN üretilen bir değer (kalıcı bir oturum kimliği DEĞİL).
- **`conversation_session_uuid`** — istemciden (`request->input('conversation_session_uuid')`)
  gelen, tarayıcı sekmesi/oturumu boyunca SABİT kalan gerçek süreklilik kimliği.

## 3.6. İlişki Haritası Otomatik Yönlendirme

`detectRelationshipChartNeededHint()` (`AiService.php:5843`) üç şart aynı anda
sağlanırsa devreye girer: (1) mesaj/konu ilişki niyeti taşıyor (`topic===
'love_relationships'` VEYA `evlilik/ilişki/sevgili/partner/uyum/sinastri/aşk/
flört/nikah/düğün/eşim/sevgilim` kelimelerinden biri — SON 3 mesaj + mevcut
mesajda aranır), (2) mevcut mesajda bir TARİH var (`GG.AA.YYYY`, `YYYY-AA-GG`
veya yılsız "15 Temmuz" biçimi), (3) bulunan tarih kullanıcının KENDİ doğum
tarihinden FARKLI. Eşleşirse LLM'e zorunlu bir "tek taraflı yorum yapma"
talimatı enjekte edilir VE frontend'e `open_relationship_modal:true` sinyali
StreamedResponse içinde gönderilir (satır 1657-1663), JS tarafında
(`panel.js`) bu sinyali yakalayıp `#relationshipModal`'ı otomatik açar.

---
