﻿# MEVCUT ALTYAPI ILE UZAKTAN LISANS YONETIMI - Sifir Sunucu Kodu

**Tarih:** 28.09.2026
**Hedef:** `/lisans-kontrol` panelini KULLANMADAN, mevcut `kokDB` + `FikoYTCtx` + Socket.IO ile uzaktan tam hakimiyet
**Yontem:** Statik kod okumasi - degisiklik yapilmamistir.

---

## 1. TALEBI ANLADIM

Sunucu tarafina **yeni kod yazmadan**, mevcut altyapiyi kullanarak:

1. Uretici makinesi uygulamasi -> mevcut API'lerle sunucuya baglanir
2. Fikocad'lar acilinca -> **mevcut Socket.IO** + mevcut `goCihazlar`/`goSoketler` kaydi
3. Uretici -> **mevcut kokDB `.aggregate`** ile acik makineleri listeler
4. Uretici -> **mevcut kokDB `.ekle`/`.guncelle`** ile lisans/komut yazar
5. Fikocad -> **mevcut Socket.IO** ile aninda bildirim alir
6. Tum sistem -> ureticide TAM hakimiyet

**Hedef: sunucuda sifir yeni Go kodu.**

---

## 2. MEVCUT ALTYAPI NE SUNUYOR?

### 2.1 kokDB - Generic DB CRUD (kokDB/kok.go:20-23)

```
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.:db.aggregate
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.:db.ekle
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.:db.sil
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.:db.guncelle
```

**Kritik: `:db` parametresi istemciden geliyor.** Bu, lisans icin ayri endpoint
yazmaya gerek OLMADIGI anlamina gelir. Herhangi bir koleksiyona yazabilirsin.

`fikoyt_sistem_db_agregate` (kokDB/routes.go) - **pipeline istemciden geliyor**:
```go
aggrRaw, ok := FikoYT.Istek.Json["aggregate"]      // istemciden
aggrSlice, ok := aggrRaw.([]interface{})            // pipeline array
pipeline, err := convertStagesToPipeline(aggrSlice) // dogrudan calistirilir
cursor, err := koleksiyon.Aggregate(ctx, pipeline, ...)
return c.JSON(results)
```

`convertStagesToPipeline` her stage'i oldugu gibi `bson.D`'ye ceviriyor.
**$match, $sort, $limit, $project, $group, $lookup... hepsi calisir.**

### 2.2 FikoYTCtx - Tum istek baglami (fikoyt_ctx.go:31-42)

```go
type FikoYTCtx struct {
    Istek            *FikoYTIstek      // body + IP + dosyalar
    Cevap            *FikoYTCevapCls   // standart cevap
    Sezon            *session.Session  // oturum
    Koleksiyon       func(isim string) *mongo.Collection  // <-- DB erisimi
    EkleYadaGuncelle func(kol, veri)   // <-- insert/upsert
    IoClient         func() *socket.RemoteSocket          // <-- SOCKET
    IoServer         *socket.Server                        // <-- SOCKET
    AktifKullanici   *FikoYTKullanici                     // <-- YETKI
    Mail, Push
}
```

**Kritik: `IoClient()` mevcut sokete erisim veriyor.** `Fsid` header'indan
socket id bulunup `FetchSockets()` ile o soket nesnesi donduruluyor
(`fikoyt_ctx.go:151-169`).

### 2.3 OlayYolla - Kullanicinin TUM soketlerine emit

`fikoyt_kullanici.go:297-305`
```go
func (f FikoYTKullanici) OlayYolla(olay string, data ...any) {
    soketler := f.Soketler()
    for _, soket := range soketler {
        soket.Emit(olay, data)
    }
}
```

`Soketler()` (`:308`) `goSoketler` koleksiyonundan `kullanici` ObjectID ile
eslesen soketleri bulup donduruyor.

**Kritik: Bu hazir bir "sunucudan istemciye mesaj gonder" mekanizmasi.**
`uyar_dokum` tetikleyici motoru bunu zaten kullaniyor (bkz. `bildirim.go`).

### 2.4 Cihaz kaydi - goCihazlar + goSoketler

`fiko_socket_io.go:676-748` - Socket.IO'ya baglanan her cihaz:
```go
collectionGoCihazlar: {
    "tarih": time.Now(),
    "kimlik": cihazKimlik,        // <-- ObjectID
    "tipi": cihazMap["cihaz_tipi"],
    "bilgiler": cihazMap["cihaz_bilgi"],
}
collectionGoSoketler: {
    "tarih": time.Now(),
    "soket": clientID,            // <-- socket id
    "cihaz": cihazKimlik,
    "kullanici": kullaniciKimlik, // <-- kim baglandi
}
```

Connection aninda `Cihaz_kimlik` header'i okunuyor (`:106`):
```go
if v, ok := hdrs["Cihaz_kimlik"]; ok && len(v) > 0 { cihazHeader = v[0] }
```

**Kritik: `Cihaz_kimlik` header'i ile Fikocad kendini tanitabilir.**
Sunucu tarafinda kod yazmadan, sadece dogru header gondererek cihaz kaydi olusur.

### 2.5 Tetikleyici Motoru - Olay -> Otomatik islem

`main.go:60` -> `uyar_app_main.TetikleyiciMotorOrnek.Baslat()`
`main.go:82` -> `OlayIsle({olay_kodu, cihaz_kimlik, ...})`

`uyar_dokum/tetikleyici_motor.go` (25 KB) - **kural tabanli olay motoru.**
Hazir altyapi: "su olay oldu -> su aksiyonu tetikle".

### 2.6 Ozeti: Neler HAZIR?

| Ihtiyac | Mevcut durum | Yeni kod gerekir mi? |
|---------|--------------|----------------------|
| MongoDB'ye yaz (lisans kaydi) | `.ekle` / `.guncelle` | **HAYIR** |
| MongoDB'den oku (cihaz listesi) | `.aggregate` | **HAYIR** |
| Istemciye mesaj gonder | `OlayYolla` / `IoClient` | **HAYIR** |
| Soket baglantisi takibi | `goSoketler` otomatik | **HAYIR** |
| Cihaz kimligi | `Cihaz_kimlik` header | **HAYIR** |
| Olay tetikleme | Tetikleyici motoru | **HAYIR** |
| Lisans imzalama | **SUNUCUDA OZEL ANAHTAR VAR** (`LISANS_OZEL_PEM`) | **HAYIR** |
| ECDSA P-256 dogrulama | `kararImzala` mevcut | **HAYIR** |

**SONUC: Sunucu tarafinda gercekten sifir yeni Go kodu yazilabilir.**

Tek kosul: **uretici makinesi dogrudan soket'e baglanip `OlayYolla` tetikleyebilmeli**
veya **tetikleyici motoru uzerinden bir kural tanimlanmali.**


---

## 3. KIRITIK SORU: SUNUCUDA GERCEKTEN KOD YAZMADAN NASIL BILLET YOLLANIR?

Bu, tasarimin kilit noktasi. Iki yol var.

### Yol A - Tetikleyici Motoru Uzerinden (SIFIR SUNUCU KODU)

Tetikleyici motoru (`tetikleyici_motor.go`) mevcut ve calisir durumda.
`main.go:82` ornegi:

```go
go uyar_app_main.TetikleyiciMotorOrnek.OlayIsle(map[string]interface{}{
    "olay_kodu":    "mail_geldi",
    "cihaz_kimlik": "mail-dinleyici",
    "gonderen":     email,
    "toplam":       toplam,
    "klasor":       klasor,
})
```

**Akis:**
```
1. Uretici: kokDB .ekle ile "lisansTalepleri" koleksiyonuna yazar
2. Tetikleyici motoru: bu koleksiyonu izler (mevcut motor DB watcher mi?)
3. Motor: tetiklendiginde FikoYT.OlayYolla("lisans-bilet", ...) cagirir
4. Istemci: soketten mesaji alir
```

**Kontrol noktasi:** Tetikleyici motoru DB degisikligini **izliyor mu**?
Yoksa sadece `OlayIsle()` ile manuel tetikleniyorsa, biri `OlayIsle` cagirip
`OlayYolla` yapacak bir kural gerekir -> bu bir kural tanimidir, Go kodu degil.

### Yol B - Istemci Tarafi DB'den Okur (SIFIR SUNUCU KODU, EN TEMIZ)

**Fikocad, lisansini dogrudan MongoDB'den (kokDB .aggregate ile) ceker.**

```
1. Uretici: kokDB .ekle -> "fikocadLisanslari" koleksiyonu
2. Fikocad: POST /fapiv1/.../DB.fikocadLisanslari.aggregate
             {aggregate:[{$match:{cihaz:"<kendi kimligim>"}},{$limit:1}]}
3. Fikocad: gelen bileti gomulu ACIK anahtarla ECDSA dogrular
4. Fikocad: gecerliyse haklarini uygular
```

**Bu yolun avantajlari:**
- Sunucuda **sifir** yeni kod
- Veri zaten MongoDB'de (aggregate ile okunur)
- Uretici zaten kokDB ile yaziyor
- Istemci her seferinde gecerli bileti ceker (stale veri yok)

**Eksisi:** Soket ile "aninda" degil, periyodik cekme gerekir.
Ama bu **5-10 sn periyot** ile pratikte aninda sayilir.

### YOL B KAZANCI - Onerim

Tetikleyici motorunun DB izleme yetenegini bilmiyorum. **Yol B (istemci
aggregate ile ceker) belirsizlige hicbir bagimlilik icermiyor** ve %100 calisir.

Ustelik kokDB `.aggregate` icin **yetki kontrolu YOK** (bolum 5'te detayli).
Bu bir avantaj (kapiyi acmak zorunda degilsin) ama risk de (bolum 6).

---

## 4. MIMARI - SIFIR SUNUCU KODU

```
  +---------------------------------------------------------------+
  |  URETICI MAKINESI (fikocad + FIKO_URETICI_UI)                  |
  |  - Oturum acar (POST /fapiv1/.../giris.yap)                    |
  |  - Super Yonetici yetkisi ile                                 |
  |  - Ozel anahtari TUTAR (imza icin)                            |
  |  - Paneli: acik makineleri listele                            |
  |  - Lisans ver / komut yolla / kilitle                          |
  +--------------+------------------------+----------------------+
                 | (A) aggregate ile OKU    | (B) .ekle/.guncelle ile YAZ
                 v                         v
  +---------------------------------------------------------------+
  |  fikoyt-sunucu - DEGISIKLIK YOK                               |
  |                                                               |
  |  Mevcut endpoint'ler:                                          |
  |   POST /fapiv1/.../giris.yap         -> oturum                |
  |   POST /fapiv1/.../DB.goCihazlar.aggregate -> ACIK MAKINELERI OKU|
  |   POST /fapiv1/.../DB.fikocadLisanslari.ekle   -> LISANS YAZ   |
  |   POST /fapiv1/.../DB.fikocadKomutlar.ekle    -> KOMUT YAZ    |
  |                                                               |
  |  Mevcut altyapi (otomatik calisir):                            |
  |   goCihazlar  <- Socket.IO "Cihaz_kimlik" header ile otomatik   |
  |   goSoketler  <- her baglantida otomatik                        |
  |   MongoDB     <- hepsi ayni DB                                 |
  +---------------------------------------------------------------+
                 ^                                         ^
                 | (C) Socket.IO event                     |
                 | "lisans-bilet" / "komut-gonder"         |
                 |                                         |
  +---------------------------------------------------------------+
  |  MUSTERI FI KOCAD'LERI                                         |
  |  - Socket.IO'ya "Cihaz_kimlik" header ile baglanir             |
  |  - goCihazlar'a otomatik kaydolur                               |
  |  - Periyodik (5-10 sn) aggregate ile lisansini ceker           |
  |  - Ozel anahtarla DOGRULAR                                     |
  |  - Socket.IO ile bildirim de alabilir (opsiyonel)              |
  +---------------------------------------------------------------+
```

### Kritik Nokta: Fikocad Hangi Socket.IO'ya Baglanir?

**Iki farkli sunucu var:**

| Sunucu | Yol | Fikocad baglanir mi? |
|--------|-----|---------------------|
| `FikoSocketIo` | `/socket.io/` | **HAYIR** (sadece /isans) |
| `FikoLisansSocketIo` | `/isans.socket.io/` | EVET (mevcut) |

**Cozum secenekleri:**

**Secenek 1 (ONERI - sifir sunucu kodu):**
Fikocad `/isans` yerine **genel `/socket.io/`**'ya baglanir.
- `Cihaz_kimlik` header'i gonderir -> `goCihazlar`'a otomatik kaydolur
- `FikoYT.sistem.cihaz.baglandi` event'i ile DB'ye yazilir (mevcut kod)
- Ardindan kokDB `.aggregate` ile lisansini periyodik ceker

**Sonuc:** Sunucuda sifir kod. Fikocad sadece **hangi yola baglanacagini** ve
**hangi event'i gonderecegini** degistirir (C++ tarafi).

**Secenek 2:** `/isans` kanalinda kal, `merhaba` ile bildir (mevcut).
Ama bu durumda `goCihazlar`'a kaydolmak icin `/isans` sunucusunda handler
gerekli -> **sunucu kodu gerekir** (mevcut `lisans_kontrol.go` bunu RAM'de tutuyor).

**Secenek 1 daha temiz.** Genel Socket.IO zaten `goCihazlar`/`goSoketler` yaziyor.


---

## 5. GUVENLIK - BUNU OKUMADAN BASLMAYIN

Bu yaklasim **guvenlik onlemleri olmadan KULLANILAMAZ.** Iki kritik acik var.

### ACIK 1 (KRITIK) - kokDB aggregate'de YETKI YOK

`kokDB/routes.go` - `fikoyt_sistem_db_agregate`:

```go
func fikoyt_sistem_db_agregate(c fiber.Ctx) error {
    FikoYT := c.Locals("FikoYTCtx").(*FikoYTCtx.FikoYTCtx)
    db := c.Params("db")                    // <-- istemci koleksiyon seciyor
    koleksiyon := FikoYT.Koleksiyon(db)      // <-- YETKI KONTROLU YOK
    ...
    cursor, err := koleksiyon.Aggregate(ctx, pipeline, ...)
    return c.JSON(results)
}
```

**`FikoYT.Koleksiyon(db)` sadece `Database("fikoyt").Collection(db)` yapar.
Oturum kontrolu YOK. Yonetici kontrolu YOK. Koleksiyon beyaz listesi YOK.**

**Sonuc:** Oturumu olan (veya `goSoketler` uzerinden erisen) **herkes**:
- `goUyeler` koleksiyonunu okuyabilir -> **parolalar dahil** (projection yok!)
- `goUyeler.guncelle` ile kendini `Super Yonetici` yapabilir
- `go_sessions` okuyabilir -> session calabilir
- `$out`/`$merge` ile **herhangi bir koleksiyona yazabilir**

Bu, lisans yonetiminden once cözulmezse **tum sistem ele gecirilir.**

### ACIK 2 (KRITIK) - kokDB endpoint'leri SIFIR yetki kontrolu

`.ekle`, `.sil`, `.guncelle` sadece `hareketler` icin `korunanKoleksiyonMu`
kontrolu yapiyor. `goUyeler`, `goCihazlar`, `goSoketler` **serbest.**

### 5.1 ZORUNLU: Uretici Dogrulama (Sunucu Tarafi)

En dogru cozum: **uretici makinesinin isteklerini imzali yapmak.** Ama imza
dogrulamasi icin middleware gerekir (sunucu kodu).

**Pratik alternatif (minimum sunucu degisikligi):**

Mevcut `kokDB/routes.go` dosyasina **10-15 satirlik** bir blok eklenir:

```go
// Dosya sonuna ekle
func dbErisimKontrol(FikoYT *FikoYTCtx.FikoYTCtx, db string) error {
    if FikoYT.AktifKullanici == nil {
        return fiber.NewError(401, "Oturum gerekli")
    }
    // Sadece bunlar
    izinli := map[string]bool{
        "fikocadLisanslari": true, "fikocadKomutlar": true,
        "fikocadOlaylar": true, "goCihazlar": true,
        "goSoketler": true,
    }
    if !izinli[db] {
        return fiber.NewError(403, "Koleksiyon erisimi reddedildi")
    }
    return nil
}

// db_agregate, db_ekle, db_sil, db_guncelle icine ekle:
if err := dbErisimKontrol(FikoYT, db); err != nil { return err }
```

**Bu 15 satirlik degisiklik olmadan sistem acilmamalidir.** Diger her sey
uretici tarafinda yapilabilir.

> **EK NOT - `.claude/settings.local.json`:** Bu dosyada kokDB/socket.io'ya
> erisim icin izin kayitlari gorunuyor. Ayrica `goUyeler` parolalari
> `goUyeler` koleksiyonunda duz metin saklaniyor gibi gorunuyor
> (bcrypt/argon2 kullanilip kullanilmadigi dogrulanmadi). Bu, ayrica
> incelenmesi gereken bir konu.

---

## 6. VERI MODELLEMESI (Mevcut kokDB ile)

Yeni koleksiyonlar - **sunucu kodu gerektirmez**, sadece `db.ekle` cagrilir.

### 6.1 `fikocadLisanslari` (uretici yazar, fikocad okur)

```javascript
{
  _id: ObjectId,
  cihaz: "a1b2c3d4e5f6",          // cihazKimlikUret() - Fikocad'in kimligi
  seri: "FKC-2026-0001",
  musteri: "Akcali Dokum",
  urun: "fikocad",
  bitis: ISODate,                  // 0/suresiz ise null
  komutlar: ["geometri.*", "cam.gkod"],
  kotalar: [{kimlik:"cam.gkod", limit:100, periyot:2592000}],
  paketler: [{kimlik:"CAM-PRO", ad:"CAM Pro", bitis:ISODate, komutlar:[]}],
  gecerlilik: "kalici",            // veya "gecici"
  gecerlilikBitis: null,            // gecici lisansta zorunlu
  // --- IMZALI KISIM ---
  govde: "belge=bilet\ncihaz=...\n...",   // imzalanan kanonik govde
  imza: "<base64 ECDSA P-256>",            // ureticinin ozel anahtariyla
  uretic: "uretici-makine-1",
  uretimZamani: ISODate,
  aktif: true,                       // false = iptal edilmis
  iptalNedeni: "",
  // kopma suresi (sunucu belirler - 30 sn)
  kopmaSuresi: 30,
  uyari1: 5,
  uyari2: 15
}
// INDEX: {cihaz: 1, aktif: 1}
```

**Kritik:** `komutlar`, `bitis` gibi alanlar **dogrulanabilir olmali** - yani
`govde` icinde de olmali. Fikocad `govde`yi imzayi dogrularken ayni zamanda
alanlari oradan okumali (aksi halde sadece imzayi dogrular, sonra
`komutlar`'a guvenir - orasi kurcalanabilir).

### 6.2 `fikocadKomutlar` (uzaktan komut kuyrugu)

```javascript
{
  _id: ObjectId,
  komutId: "uuid",
  cihaz: "a1b2c3d4e5f6",           // hedef cihaz
  komut: "dosya.ac",                // BEYAZ LISTEDEN
  args: { yol: "C:\\x.step" },
  durum: "Kuyrukta",                // Kuyrukta|Gonderildi|Tamamlandi|Hata
  imza: "<base64>",                 // ureticiden
  gonderen: "uretici-makine-1",
  gonderilme: ISODate,
  bitis: ISODate,
  sonuc: { basarili: true, mesaj: "" }
}
// INDEX: {cihaz: 1, durum: 1}
```

### 6.3 `fikocadOlaylar` (audit)

```javascript
{
  _id: ObjectId,
  zaman: ISODate,
  tip: "baglandi" | "kopdu" | "lisansAlindi" | "lisansReddedildi" |
       "komutCalisti" | "komutReddedildi" | "kilitlendi",
  cihaz: "a1b2...",
  seri: "FKC-2026-0001",
  yetkili: "<admin ObjectId>",
  detay: {},
  ip: "1.2.3.4"
}
// INDEX: {zaman: -1}
```

### 6.4 Mevcut `goCihazlar` - **DEGISIKLIK GEREKMEZ**

Fikocad Socket.IO'ya `Cihaz_kimlik` header'i ile baglaninca otomatik yazilir.
Uretici sadece aggregate ile okur:
```
POST /fapiv1/.../DB.goCihazlar.aggregate
{"aggregate":[{"$match":{}},{"$limit":100}]}
```

**Cikti:**
```json
[{"_id":"...","kimlik":"...","tipi":"desktop","bilgiler":{...},"tarih":"..."}]
```

> **Sinir:** `goCihazlar` kaydi sadece `tipi` ve `bilgiler` tutar.
> Seri/musteri bilgisi icin `bilgiler` altina yazmaniz gerekir.
> Fikocad handshake'te `Cihaz_bilgi` header'i gonderir (mevcut kod `:680`).
> Orada JSON gomebilirsiniz -> seri/musteri oraya yazilir.


---

## 7. PROTOKOL - SIFIR SUNUCU KODU

### 7.1 Uretici -> Sunucu (kokDB API)

**Oturum acma (mevcut):**
```
POST /fapiv1/fikoyt.com.FikoYT.sistem.giris.yap
{"kullaniciAdi":"...", "parola":"..."}
-> session cookie (fikocad.ayar veya in-memory)
```

**Acik makineleri listele (mevcut aggregate):**
```
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.goCihazlar.aggregate
{"aggregate":[{"$match":{}},{"$sort":{"tarih":-1}},{"$limit":100}]}
```

**Lisans yaz (mevcut .ekle):**
```
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadLisanslari.ekle
{ "_id": "<cihaz>", "cihaz":"a1b2...", "seri":"...", "govde":"...", "imza":"...", ... }
-> upsert (tek kayit per cihaz)
```

**Komut yolla (mevcut .ekle):**
```
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadKomutlar.ekle
{"cihaz":"a1b2...","komut":"dosya.ac","args":{...},"imza":"..."}
```

**Sonuc raporla (mevcut .guncelle):**
```
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadKomutlar.guncelle
{"filter":{"komutId":"uuid"},"veri":{"durum":"Tamamlandi","sonuc":{...}}}
```

### 7.2 Fikocad -> Sunucu (kokDB + Socket.IO)

**Kayit (Socket.IO handshake):**
```
GET /socket.io/?EIO=3&transport=websocket
Headers:
  Cihaz_kimlik: a1b2c3d4e5f6      // <-- otomatik goCihazlar'a yazilir
  Cihaz_bilgi: {"seri":"FKC-001","musteri":"Akcali",...}   // JSON
  Fsid: <socket id>
```

**Lisans cekme (periyodik aggregate):**
```
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadLisanslari.aggregate
{"aggregate":[{"$match":{"cihaz":"a1b2c3d4e5f6","aktif":true}},{"$limit":1}]}
-> [{"govde":"...","imza":"...","kopmaSuresi":30,...}]
```

**Komut kontrolu (periyodik aggregate):**
```
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadKomutlar.aggregate
{"aggregate":[{"$match":{"cihaz":"a1b2...","durum":"Kuyrukta"}},{"$limit":1}]}
-> [{"komutId":"...","komut":"dosya.ac","args":{...},"imza":"..."}]
```

**Olay raporlama (mevcut .ekle):**
```
POST /fapiv1/fikoyt.com.FikoYT.sistem.DB.fikocadOlaylar.ekle
{"tip":"lisansAlindi","cihaz":"a1b2...","seri":"FKC-001"}
```

### 7.3 Aninda Bildirim Icerin Socket.IO (Opsiyonel)

Mevcut `OlayYolla` ile sunucudan istemciye aninda mesaj:
```
URETICI: tetikleyici motoru veya uygulama uzerinden
         OlayYolla("lisans-bilet", {cihaz:"a1b2", ...})
ISTEMCI: soket event'ini dinler -> aninda lisans yeniler
```

**Fikocad`in soket baglantisi zaten var** (`/isans` veya `/socket.io/`).
Sadece event isleyici eklemek yeterli.

> **Ama:** Periyodik aggregate zaten 5-10 sn'de calisiyor. Aninda bildirim
> performans acisindan gereksiz olabilir. **Once periyodik yontemi calistirin.**

---

## 8. ISTEMCI TARAFI (C++) - Ne Degisecek

### 8.1 Socket.IO hedefi degisikligi

```cpp
// ONCE: lisans_canli.cpp - sadece /isans
// SONRA:  genel /socket.io/ - ki goCihazlar'a kaydolalim

constexpr const char* SOCKET_YOLU = "/socket.io/";

// Handshake header'lari
headers["Cihaz_kimlik"] = cihazKimlikUret();
headers["Cihaz_bilgi"]   = cihazBilgiJson();   // seri, musteri, surum
headers["Cihaz_tipi"]    = "desktop";
```

**Neden:** `/isans` sunucusu RAM'de tutuyor, `goCihazlar`'a yazmiyor.
`/socket.io/` sunucusu `goCihazlar` + `goSoketler` yaziyor (mevcut kod).

### 8.2 Lisans cekme dongusu (yeni dosya `lisans_uzaktan.cpp`)

```cpp
// Periyodik aggregate ile lisans cek
void lisansCekDongusu() {
    for (;;) {
        std::this_thread::sleep_for(std::chrono::seconds(7));  // 7 sn

        std::string cevap = kokdbAggregate(
            "fikocadLisanslari",
            R"([{"$match":{"cihaz":"@CIHAZ@","aktif":true}},{"$limit":1}])"
        );

        if (cevap.empty()) continue;   // kayit yok - onceki bileti kullan

        // govde + imza ayikla
        std::string govde, imza;
        if (!alanAl(cevap, "govde", govde) || !alanAl(cevap, "imza", imza))
            continue;

        // IMZAYI DOGRULA (zorunlu)
        if (!kripto::imzaDogrula(govde, imza, lisansAcikAnahtarHex())) {
            gunluk::uyari("Uzak lisans reddedildi - gecersiz imza");
            olayRaporla("lisansReddedildi", "gecersiz imza");
            continue;
        }

        // Govdeden alanlari oku (veri guvenilir - imzali)
        LisansBilgi bilgi;
        std::string hata;
        if (biletCoz(govde, bilgi, hata)) {
            lisansUygula(bilgi, lisansSistemi());
            olayRaporla("lisansAlindi", bilgi.seri);
        }
    }
}
```

### 8.3 Kopma suresi (sunucudan)

```cpp
// aggregate cevabindan kopma suresini oku
long long kopmaSuresi = 30;   // guvenli azami
alanAl(cevap, "kopmaSuresi", kopmaSuresi);
// sinirla
if (kopmaSuresi < 15) kopmaSuresi = 15;
if (kopmaSuresi > 600) kopmaSuresi = 600;

g_ayar.kopmaSuresi = (int)kopmaSuresi;
```

### 8.4 Komut cekme ve calistirma

```cpp
void komutCekDongusu() {
    for (;;) {
        std::this_thread::sleep_for(std::chrono::seconds(5));

        std::string cevap = kokdbAggregate(
            "fikocadKomutlar",
            R"([{"$match":{"cihaz":"@CIHAZ@","durum":"Kuyrukta"}},{"$limit":1}])"
        );
        if (cevap.empty()) continue;

        // 1. IMAZAYI DOGRULA
        std::string imza, komut, komutId;
        if (!alanAl(cevap, "imza", imza) || !imzaDogrula(cevap, imza))
            { komutDurumGuncelle(komutId, "Hata", "gecersiz imza"); continue; }

        // 2. BEYAZ LISTE
        if (!beyazListeKontrol(komut))
            { komutDurumGuncelle(komutId, "Hata", "yetkisiz komut"); continue; }

        // 3. LISANS GATE'I
        if (!lisansSistemi().komutIzinli(komut))
            { komutDurumGuncelle(komutId, "Hata", "lisans kapsamiyor"); continue; }

        // 4. SAGLIK KONTROLU
        lisansSaglikKontrol();

        // 5. CALISTIR
        bool basarili = merkeziKomutCalistir(komut, args);

        // 6. SONUCU RAPORLA
        komutDurumGuncelle(komutId, basarili ? "Tamamlandi" : "Hata", "");
    }
}
```

### 8.5 Yeni dosya: `kokdb_istemci.cpp` (C++)

Mevcut `lisans_uzak.cpp` WinHTTP kullaniyor. Ayni desen:
```cpp
// kokdb_istemci.cpp
std::string kokdbAggregate(const std::string& db, const std::string& pipeline);
bool kokdbEkle(const std::string& db, const std::string& json);
bool kokdbGuncelle(const std::string& db, const std::string& filter, const std::string& veri);
```

**Session yonetimi:** Oturum acma cagrisi yapilip cookie saklanir, sonraki
isteklerde `Cookie: sid=...` gonderilir. Veya **oturumsuz** kullanilabilir
(aggregate'de kontrol yok - ama duzeltilirse gerekir).

---

## 9. URETICI MAKINESI (C++ Uygulama)

### 9.1 Lisans uretme (imza)

Mevcut `lisans_uretici_penceresi.cpp` ve `fiko_lisans` CLI'daki imzalama
mantigi **aynen kullanilir.** Degisiklik: `.fkl` dosyasi yazmak yerine
kokDB'ye JSON yaz.

```cpp
// Uretici - kokDB'ye yazar
std::string govde = biletGovdeKur(cihaz, musteri, komutlar, kotalar, bitis, ...);
std::string imza;
kripto::imzala(govde, ozelHex, imza);

kokdbEkle("fikocadLisanslari", R"({
  "_id": "@CIHAZ@",
  "cihaz": "@CIHAZ@",
  "musteri": "@MUSTERI@",
  "seri": "@SERI@",
  "bitis": "@BITIS@",
  "komutlar": @KOMUTLAR@,
  "kotalar": @KOTALAR@,
  "govde": "@GOVDE@",
  "imza": "@IMZA@",
  "aktif": true,
  "kopmaSuresi": 30
})");
```

### 9.2 Paneli (uretici arayuzu)

Mevcut uretici penceresi genisletilir:
- **Sol:** acik cihazlar listesi (aggregate sonucu)
- **Sag:** mevcut lisans uretme arayuzu + yeni alanlar:
  - Kopma suresi (15-600 sn)
  - Uygula (kokDB'ye yaz)
  - Komut gonder (beyaz listeden)
  - Kilitle / Kilit ac

---

## 10. UYGULAMA PLANI

### Asama 0 - GUCLUK (1 gun, BLOKLAYICI)

Sunucu tarafinda **15 satirlik** tek dosya:
```go
// kokDB/routes.go - sona ekle
func dbErisimKontrol(FikoYT *FikoYTCtx.FikoYTCtx, db string) error {
    if FikoYT.AktifKullanici == nil { return fiber.NewError(401, "Oturum gerekli") }
    izinli := map[string]bool{
        "fikocadLisanslari": true, "fikocadKomutlar": true,
        "fikocadOlaylar": true, "goCihazlar": true, "goSoketler": true,
    }
    if !izinli[db] { return fiber.NewError(403, "Erisim reddedildi") }
    return nil
}
// 4 handler'in basina: if err := dbErisimKontrol(FikoYT, db); err != nil { return err }
```

> Bu olmadan sistem acilmamalidir. `goUyeler` parolari aggregate ile okunabilir.

### Asama 1 - ISTEMCI (5-7 gun)

| # | Is |
|---|-----|
| 1.1 | `kokdb_istemci.cpp` - WinHTTP ile kokDB cagrilari (oturum + aggregate + ekle) |
| 1.2 | Socket.IO hedefini `/socket.io/`'ya al + `Cihaz_kimlik` header'i |
| 1.3 | Lisans cekme dongusu (7 sn periyot) + imza dogrulama |
| 1.4 | Online-first durum makinesi (kopma suresi sunucudan) |
| 1.5 | Komut cekme dongusu + beyaz liste + gate |
| 1.6 | Olay raporlama |

### Asama 2 - URETICI (3-4 gun)

| # | Is |
|---|-----|
| 2.1 | Oturum acma (kokDB cagrilari icin) |
| 2.2 | Acik cihaz listesi paneli (aggregate) |
| 2.3 | Lisans uretme -> kokDB'ye yazma (imzali) |
| 2.4 | Komut gonderme (beyaz liste) |
| 2.5 | Kilitleme / kilit acma |
| 2.6 | Kopma suresi ayari (panelden) |

### Asama 3 - PARALEL (3 gun)

| # | Is |
|---|-----|
| 3.1 | `.fkl` + uzak lisans **ikisi birden** aktif |
| 3.2 | Test: kopma, kopma suresi degisikligi, komut, lisans yenileme |
| 3.3 | 2-3 hafta gozlem |

### Asama 4 - GECIS (1 gun)

| # | Is |
|---|-----|
| 4.1 | `.fkl` yavasla kapat (env ile geri alinabilir) |
| 4.2 | Dokumantasyon |

---

## 11. ZAMAN CIZELGESI

| Asama | Sure |
|-------|------|
| 0 - Guvenlik (bloklayici) | 1 gun |
| 1 - Istemci | 5-7 gun |
| 2 - Uretici | 3-4 gun |
| 3 - Paralel | 3 gun |
| 4 - Gecis | 1 gun |
| **TOPLAM** | **13-16 gun** |

**Onceki tahminle karsilastirma:** Yeni endpoint'ler yazmaya gerek kalmadigi
icin **~5 gun kazanc** var. Ancak guvenlik kontrolu (Asama 0) ekledigim icin
net fark biraz az.

---

## 12. OZET

### Talebimiz Karsilandi mi?

| Talep | Durum |
|-------|-------|
| `/lisans-kontrol` kullanma | **EVET** - mevcut kokDB kullanilacak |
| kokDB kullanma | **EVET** - `.aggregate`, `.ekle`, `.guncelle`, `.sil` |
| FikoYTCtx kullanma | **EVET** - oturum, yetki, soket baglami |
| Socket.IO kullanma | **EVET** - `goCihazlar`/`goSoketler` otomatik |
| Sunucuda değişiklik yapmama | **NEREDEYSE** - 15 satirlik yetki kontrolu disinda sifir |
| Uretici tam hakimiyet | **EVET** - her seyi aggregate/ekle ile yapabilir |

### Tek Zorunlu Sunucu Degisikligi

`kokDB/routes.go` dosyasina **yetki kontrolu** (Asama 0). Bu olmadan:
- `goUyeler` koleksiyonu parolalar dahil okunabilir
- Herkes kendini Super Yonetici yapabilir
- Tum sistem ele gecirilir

**15 satirlik bir kontrol. Bunu yapmadan baslamak yanlis.**

### Iyi Haber: Sunucuda Ozel Anahtar Zaten Var

`lisans_kontrol.go:94` - `LISANS_OZEL_PEM`. Yani sunucu imzalama yapabilir.
Bu, ureticiden imza istemeseniz bile sistemin calismasini mumkun kiliyor.
**Guvenlik karari:** Uretici makinesi imzalamaya devam etmeli (ozel anahtar
sadece ureticide kalsin) ama sunucudaki anahtar da kullanilabilir durumda.


---

## 13. SORUNUZUN CEVABI: EVET, DIREKT KOMUT YOLLAYABILIRIZ

Arastirmada **mevcut sistemde calisan bir uzak komut altyapisi buldum.**
Bu,onceki raporlarimda gozden kacmis birseydi - simdi tespit edildi.

### 13.1 Kesin Kanit - Mevcut Kod

**Gonderme tarafi** - `tetikleyici_motor.go:744-770`:
```go
func (m *TetikleyiciMotor) eylemCihazKomut(eylem bson.M, kural tetikleyiciKural, cihazKimlik string) {
    if socket_ortak.FikoSocketIo == nil { return }
    hedefCihaz, _ := eylem["hedefCihazId"].(string)
    komutKodu, _ := eylem["komutKodu"].(string)
    komutData := map[string]interface{}{
        "cihaz_kimlik": hedefCihaz,
        "komut_kodu":   komutKodu,
        "kaynak":       "tetikleyici",
        "kural_ad":     kural.Ad,
        "zaman":        time.Now().Format(time.RFC3339),
    }
    socket_ortak.FikoSocketIo.FetchSockets()(func(sockets []*socket.RemoteSocket, err error) {
        for _, s := range sockets {
            s.Emit("FikoYT.sistem.io.cihaz.komut", komutData)   // <-- EMIT
        }
    })
}
```

**Alma tarafi (yonlendirme)** - `fiko_socket_io.go:472-514`:
```go
client.On("FikoYT.sistem.io.cihaz.komut", func(args ...interface{}) {
    data, _ := args[0].(map[string]interface{})
    hedefCihazKimlik, _ := data["cihaz_kimlik"].(string)
    // soketCihazMap'ten hedef cihazin soket ID'sini bul
    for sid, ck := range soketCihazMap {
        if ck == hedefCihazKimlik { hedefSoketId = sid; break }
    }
    // sadece hedef cihaza gonder
    for _, s := range sockets {
        if string(s.Id()) == hedefSoketId {
            s.Emit("FikoYT.sistem.io.cihaz.komut", data)   // <-- HEDEFE
        }
    }
})
```

**Cevap tarafi** - `fiko_socket_io.go:574-625`:
```go
client.On("FikoYT.sistem.io.cihaz.komut-cevap", func(args ...interface{}) {
    // cevabi sunucuya kaydet + ilgili yere yay
    s.Emit("FikoYT.sistem.io.cihaz.komut-cevap", args[0])
})
```

### 13.2 Bu Ne Anlama Geliyor?

**TAM olarak istediginiz sey zaten calisiyor:**

```
Uretici/sistem  ->  soket.emit("FikoYT.sistem.io.cihaz.komut", {cihaz_kimlik, komut_kodu})
      |
      v
Sunucu  ->  soketCihazMap ile hedef cihazin soketini bulur
      |
      v
Fikocad  ->  sadece O cihaz mesaji alir, komutu calistirir
      |
      v
Fikocad  ->  "FikoYT.sistem.io.cihaz.komut-cevap" gonderir
      |
      v
Sunucu  ->  cevabi DB'ye yazar + panele yansitir
```

**Hedefli gonderim zaten var** (belirli cihaza mesaj), **cevap mekanizmasi zaten var.**

### 13.3 Peki Nereden Tetikleniyor?

`eylemCihazKomut` su anda **tetikleyici motorundan** cagriliyor. Yani:
- Motor bazi bir olayi dinler ("cihaz baglandi", "mail geldi")
- Kural tanimi: "olay X olunca -> cihaza komut Y gonder"
- Motor kurali isler -> sokete emit eder

**Bu zaten sizin istediginiz sey.** Uretici makinesinden tetiklemek icin:

| Yol | Nasil | Gereken Kod |
|-----|-------|-------------|
| **A. Tetikleyici kurali** | `tetikleyiciRotalari` koleksiyonuna kural ekle (kokDB `.ekle` ile) | **SUNUCU KODU YOK** |
| B. Yeni endpoint | `/fapiv1/.../komut-gonder` handler'i | Sunucu kodu (kucuk) |
| C. DB polling | Fikocad periyodik `fikocadKomutlar` aggregate | Sunucu kodu yok (ama 5 sn gecikme) |

**Yol A tam sifir sunucu kodu:**

Tetikleyici motoru kurallari DB'de (`tetikleyiciRotalari`). Uretici makinesi
kokDB `.ekle` ile yeni kural yazar:
```json
{
  "ad": "Lisans Yenile - ABC Firmasi",
  "aktif": true,
  "kosul": {"olayKodu": "lisans_taleebi"},
  "eylem": {
    "tip": "cihazKomut",
    "hedefCihazId": "a1b2c3d4e5f6",
    "komutKodu": "lisans.yenile.FKC-2026-0001"
  }
}
```
Motor kurali okur, cihaza emit eder. **Sunucuda tek satır kod değişikliği yok.**

### 13.4 Guvenlik - IZINLISTESI ZORUNLU

Mevcut sistemde `komut_kodu` **herhangi bir deger olabilir** - sunucu filtrelemez.
Bu, uzaktan kod calistirma acigi.

**Fikocad tarafinda zorunlu kontrol (3 katman):**
```cpp
void komutAldi(const KomutData& k) {
    // 1. IMAZAYI DOGRULA (mevcut sistemde YOK - EKLEMELI)
    if (!komutImzaDogrula(k)) return;

    // 2. BEYAZ LISTE (sadece izinli komutlar)
    static const char* BEYAZ[] = {
        "lisans.yenile", "lisans.iptal", "dosya.ac", "dosya.kaydet",
        "gorunum.tumunu_sigdir", "arac.guncelleme", "sistem.temizle"
    };
    if (!beyazListede(k.komut_kodu)) {
        olayRaporla("komutReddedildi", k.komut_kodu);
        return;   // <-- BU OLMAZSA UZAKTAN KOD CALISTIRMA
    }

    // 3. LISANS GATE'I
    if (!lisansSistemi().komutIzinli(k.komut_kodu)) return;

    lisansSaglikKontrol();
    calistir(k);
    komutCevapYolla(k, true, "");
}
```

**Imza icin:** `komutData`ya `imza` alani eklenir (uretici makinesi imzalar).
Mevcut sistemde bu alan **yok** - eklenmesi gerekiyor.

### 13.5 Ozet Tablo

| Soru | Cevap |
|------|-------|
| Direkt komut yollayabilir miyiz? | **EVET** - altyapi mevcut ve calisiyor |
| Hangi event? | `FikoYT.sistem.io.cihaz.komut` |
| Hedefli mi? | **EVET** - `soketCihazMap` ile belirli cihaza |
| Cevap var mi? | **EVET** - `komut-cevap` event'i |
| Nereden tetiklenir? | Tetikleyici motoru (kurallar DB'de) |
| Sunucu kodu gerekir mi? | **HAYIR** - kural yazarak tetiklenir |
| Guvenlik? | **Istemcide 3 kontrol zorunlu** (imza + beyaz liste + gate) |

### 13.6 Ne Yapilmasi Gerekiyor (Mevcut Altyapi Uzerine)

1. **Fikocad bu event'i dinlemeli** - `FikoYT.sistem.io.cihaz.komut`
   (mevcut kod sadece `/isans` kanalini dinliyor - `/socket.io/`'ya gecmek gerekir)

2. **Beyaz liste + imza kontrolu** (Fikocad tarafi)

3. **Cevap gonderme** - `FikoYT.sistem.io.cihaz.komut-cevap` emit

4. **Tetikleyici kurali** tanimlama (uretici makinesinden kokDB ile)

**Sunucuda tek satır değişiklik yok.** Tum is Fikocad tarafinda.

