# Packages/RbnFactory — içerik fabrikası ayarı ve VPS sözleşmesi > **Kapsam:** Bu sayfa yalnız fabrika ayar dosyasındaki `direction` bloğunun VPS'e giden alanlarını anlatır. Paketin tam dosya envanteri henüz yazılmadı. > **Kaynak klasör:** `Packages/RbnFactory/` · ayar örneği: `Packages/RbnFactory/factory-settings.example.php` ## `direction.site_domain` — sitenin kendi alan adı | Ayar | Jobs yanıtı alanı | Eşleme | |---|---|---| | `direction.site_domain` | `site_alan_adi` | `Models/FactoryContract.php` (`FIELDS`), değer `Services/FactoryService.php` içinde `trim` edilerek yazılır | * **Biçim:** yalnız alan adı, ör. `site.com` (şemasız, `www` öneksiz). * **Ne işe yarar:** VPS, yazının CTA ve bağlantılarında yalnız bu alan adını bilir; başka bir alan adı içeren yazıyı **reddeder**. Böylece model alan adı uyduramaz. * **Boş bırakılırsa:** `site_alan_adi` boş gider; VPS tarafında alan adı kuralı uygulanmaz (eski davranış). Canlı sitede doldurmanız önerilir. * **Örnek:** ```php 'direction' => [ 'site_note' => 'Sitenin konusu ve bölgesi hakkında kısa not', 'location' => '', 'site_domain' => 'site.com', ], ``` ## Yazı tarihleri — `published_at` ve `modified_at` (ZORUNLU sütunlar) İçerik/yazı tablolarında `published_at` (yayın tarihi) ve `modified_at` (değişiklik tarihi) **zorunlu sütunlardır**. Çalışma zamanında "sütun var mı" diye bakan dal, `created_at`/`updated_at` yedeği ve "sütunsuz sitede eski davranış" YOKTUR: sütunları olmayan site blog sayfalarını çalıştıramaz (kurulum: aşağıda `factory:published-at --apply`). Yayındaki (`is_active = 1`) yazıda iki sütun HER ZAMAN dolu; pasif yazıda ikisi de NULL (public yüzeyde gösterilmez). ### Kurallar (tek yol) | Olay | `published_at` | `modified_at` | Kod | |---|---|---|---| | İlk yayın (fabrika cron'u) | şimdi | **`published_at` ile aynı değer** | `FactoryPostRepository::publishPost()` (yalnız `published_at IS NULL` iken; yeniden yayınlayan iş tarihi ileri kaydırmaz) | | İlk yayın (panel / API: `savePost` ile `is_active = 1`) | şimdi | `published_at` ile aynı | `ContentServiceTrait::savePost()` → `stampPostDates()` | | İlk yayın (otomatik görev hattı) | şimdi | `published_at` ile aynı | `AutoTaskTrait::publishPost()` | | Yayındaki yazının içeriği değişti | değişmez | şimdi | `ContentServiceTrait::savePost()` (içerik alanı eski değerden farklıysa) | | Sayaç, aynı içerikle kayıt, kategori/görünürlük değişimi | değişmez | değişmez | — | İçerik alanları: `title`, `slug`, `summary`, `content`, `image`, `images`, `seo_title`, `seo_description`, `seo_keywords`, `cta`, `faqs`, `pros_cons`, `author_comment` (JSON alanlar biçimden bağımsız karşılaştırılır). Panel (RbnStudio) düzenlemesi ve fabrika yenilemesi (`FactoryAuditService::applyRenewal`) aynı `savePost` yolundan geçer; yenileme `published_at`'i ileri taşımaz. ### Okuma (`FormatHelper`, yedeksiz) * `FormatHelper::publishedAt($post)` yalnız `published_at` okur. Görünen yayın tarihi (`enrichPost`), JSON-LD `datePublished`, RSS `pubDate`, kategori sırası ve liste/sitemap sıralaması buradan gelir. * `FormatHelper::modifiedAt($post)` yalnız `modified_at` okur. JSON-LD `dateModified`, sitemap `lastmod` ve `enrichPost` içindeki `modified_date/time/relative` (eski `updated_*` adları aynı değerle) buradan gelir. * `created_at` ve `updated_at` HİÇBİR içerik tarihi çıktısında okunmaz (`updated_at` sayfa görüntüleme sayacı gibi teknik yazmalarla da yenilenir: `ON UPDATE CURRENT_TIMESTAMP`). * Liste sorgusu (`ContentQueryTrait::getPosts()`) `published_at, modified_at` seçer; varsayılan sıra `ORDER BY published_at DESC`. * Yazı detayında "Güncelleme: …" metnini yalnız `modified_date` yayın gününden farklıysa gösterin; liste kartlarında göstermeyin. ### Sitemap, feed, crawler | Çıktı | Kaynak | |---|---| | Sitemap `lastmod` | Yalnız satırın `modified_at` değeri (`FormatHelper::modifiedAt()` → `SitemapResolver::entryLastmod()`): yazı, sayfa, ürün, kategori aynı kural. Tarihi olmayan girdide (ana sayfa, veri satırı olmayan rota) `` etiketi HİÇ basılmaz; `SitemapBuilder` üretim anı yazmaz. Sitemap indeksi her alt sitemap için o dosyadaki en büyük `lastmod` değerini yazar (`SitemapResolver::getSummary()`); girdisinde tarih olmayan alt sitemapte etiket yoktur. Özel `getSitemapEntries()` kendi sorgusunda `published_at` ile sıralar ve `modified_at` seçer. Sayfa/ürün/kategori tabloları için bkz. `docs/Core/Database/Models.md` §5.1 (`$contentFields`). | | RSS `pubDate` | `FormatHelper::publishedAt()` → `FormatHelper::toRfc822()` (`FeedResolver`); tarih yoksa `` yazılmaz, `lastBuildDate` en yeni yazının tarihidir (üretim anı yazılmaz) | | JSON-LD `datePublished` / `dateModified` | `publishedAt()` / `modifiedAt()` → `SchemaBuilder::schema()` değeri `FormatHelper::toIso8601()` ile yazar (`2026-10-13T10:00:03+03:00`, ISO 8601 + saat dilimi); boş tarih alanı düşer, `blogPost()` tarih uydurmaz | | İçerik adresi (sitemap `loc`, feed `link`/`guid`, llms.txt) | TEK üretici `CrawlerResolver::resolveEntryPath()`: girdinin `url` değeri, yoksa kaynağın `prefix` ayarı (`/blog/{category_slug}`) + `slug`; `{category_slug}` girdinin `category_id` değerinden `resolveCategorySlugMap()` ile çözülür. Önek varsayımı yoktur: `CrawlerMap` kaynağında `prefix` verin; canonical bu adresle aynı olmalıdır | | Kaynak sırası (sitemap/feed/llms) | `CrawlerMap` kaynağında `order_by` (içerik kaynağı için `'order_by' => 'published_at'`); verilmezse birincil anahtar (`CrawlerResolver::callServiceMethod()`). Tarih sütunu varsayımı yoktur. | `CrawlerMap` içerik kaynağı örneği: ```php 'blog' => [ 'model' => \Rbn\Project\App\Models\BlogPostModel::class, 'entries_method' => 'getSitemapEntries', 'order_by' => 'published_at', // ... ], ``` ### Sütunları kurma ve eski yazıları doldurma ``` php rbn factory:published-at --project= [--type=blog] # plan (salt okunur) php rbn factory:published-at --project= [--type=blog] --apply # sütunlar + indeks + doldurma ``` * `--apply` sütunları ekleyen TEK yerdir (`FactorySchemaHandler`): `published_at DATETIME NULL` (+ indeks) ve `modified_at DATETIME NULL`; sütun sırası kuralı için aşağıya bakın. * Doldurma yalnız **yayındaki** yazılara dokunur: `published_at` boşsa fabrika yazısında `app_factory_jobs.published_at`, yoksa `scheduled_at`, o da yoksa `created_at` ile doldurulur ve `modified_at` aynı değere eşitlenir; `published_at` dolu, `modified_at` boşsa `modified_at = published_at` olur. `updated_at` DEĞİŞMEZ (`keepUnchanged`). Pasif yazılara dokunulmaz. İkinci koşu 0 satır yazar. * **Eski sitelerde bu komut çalıştırılmadan blog çalışmaz** (geriye dönük uyum yok). Yüklemeden sonra `rbn cache:clear` gerekir (sayfa, liste ve sitemap önbelleği). ### Sütun sırası Tablo kolon sırası kuralı (`docs/kavramlar/03-veritabani-ve-kiracilik.md` §8): son iki sütun sırayla `updated_at`, `created_at`. `published_at` ve `modified_at` `updated_at`'ten ÖNCE eklenir (`FactorySchemaHandler::ensurePostPublishedAt()` / `ensurePostModifiedAt()`, `AFTER` koddan hesaplanır). Sütun yanlış yerdeyse (örneğin sonda) `factory:published-at --apply` `MODIFY … AFTER` ile taşır ve `created_at`'i tablonun sonuna alır (`ensurePostTimestampTail()`; veri, tip ve `ON UPDATE CURRENT_TIMESTAMP` korunur); plan çıktısındaki "sütun sırası" sütunu doğru/YANLIŞ gösterir. ### Kapı testi `fw-regresyon\birim\fw_icerik_tarihi_tek_kaynak.php`: K1-K2 tarih çıktısında `updated_at` kırmızı; K3 `publishedAt()` / `modifiedAt()` yalnız kendi sütununu okur; K4 `modified_at` ham okuması yalnız izinli dosyalarda; **K5** framework (Core/Packages/Bundles) ve proje kodunda tarih bağlamında çalışma zamanı sütun denetimi (`hasColumn`, `hasPublishedAt`…), `published_at`/`modified_at` için `??` yedeği, aynı satırda `created_at`/`updated_at` yedeği ve `COALESCE(published_at|modified_at|created_at)` YASAK (baseline yok; tek istisna sütunu ekleyen `FactorySchemaHandler`). Sürüm notu: [`.github/UPGRADING.md`](../../.github/UPGRADING.md) ve [`.github/CHANGELOG.md`](../../.github/CHANGELOG.md) `[Unreleased]`.