No description
Find a file
2026-10-09 14:33:26 +03:00
.github/instructions Wizard dosya isimlerinde çakışması problemi giderildi 2026-10-09 14:33:26 +03:00
api Administration -> Onaycılar 2026-10-09 14:23:24 +03:00
configs Wizard dosya isimlerinde çakışması problemi giderildi 2026-10-09 14:33:26 +03:00
ui Menu yapısı ve style değiştirildi. 2026-10-08 14:20:54 +03:00
.gitignore ollama chat webui kurulumu 2026-09-17 15:24:51 +03:00
claude.md Wizard dosya isimlerinde çakışması problemi giderildi 2026-10-09 14:33:26 +03:00
README.md Administration -> Onaycılar 2026-10-09 14:23:24 +03:00

Sozsoft Platform

Çalışma zamanında yapılandırılabilen, çok kiracılı (multi-tenant) low-code uygulama motoru.

Sozsoft Platform, klasik bir "kod yaz–derle–yayınla" uygulaması değildir. Ekranlar, menüler, route'lar, yetkiler, veri kaynakları, iş akışları ve hatta API endpoint'leri veritabanındaki konfigürasyon kayıtlarından üretilir. Yeni bir modül veya ekran eklemek çoğu durumda tek satır React/C# kodu yazmadan, yalnızca konfigürasyon ile mümkündür.

Temel ilke: Configuration first, code last. Bu ilkenin operasyonel karşılığı ve yapay zeka ajanları için bağlayıcı kurallar .github/instructions/ai.instructions.md dosyasındadır.


İçindekiler

  1. Platform Ne Yapar?
  2. Mimari Genel Bakış
  3. Teknoloji Yığını
  4. Depo Yapısı
  5. Hızlı Başlangıç
  6. Yapılandırma (Configuration)
  7. Low-Code Motoru: ListForm Ekosistemi
  8. Developer Kit
  9. Dinamik Menü, Route ve Yetki Modeli
  10. Modül Kataloğu
  11. Kimlik, Oturum ve Güvenlik
  12. Çok Kiracılılık (Multi-Tenancy)
  13. Bildirim ve Entegrasyonlar
  14. Arka Plan İşleri
  15. Raporlama
  16. Dosya Yönetimi ve CDN
  17. Gerçek Zamanlı Özellikler
  18. Frontend Mimarisi
  19. API ve Swagger
  20. Dağıtım (Deployment)
  21. Geliştirme Standartları ve Sık Kullanılan Komutlar
  22. Sorun Giderme
  23. Bilinen Sınırlar ve Notlar

1. Platform Ne Yapar?

Yetenek Açıklama
Dinamik liste/form ekranları ListForm + ListFormField kayıtlarıyla tanımlanan; grid, pivot, ağaç, grafik, gantt, scheduler, kart ve todo görünümlerini destekleyen ekranlar.
Dinamik menü ve route Menüler ve route'lar veritabanından okunur; frontend bunları çalışma zamanında React Router'a bağlar.
Yetki tabanlı görünürlük Menü, ekran, sütun ve buton seviyesinde ABP permission kontrolü.
SQL Query Manager Tarayıcı içinden SQL nesne gezgini, sorgu editörü, sonuç grid'i ve tablo tasarımcısı.
Custom / CRUD Endpoint Kod yazmadan, SQL veya tablo tanımından REST endpoint üretimi.
Dynamic Service Roslyn ile çalışma zamanında derlenen ve controller olarak kaydedilen C# ApplicationService'ler.
Custom Component Babel ile tarayıcıda derlenen, veritabanında saklanan React bileşenleri ve bunların route'ları.
İş akışı / onay ListForm üzerinde görsel workflow tasarımcısı, koşullu dallanma ve çok kişili onay adımları.
Çok kiracılılık Tenant başına ayrı bağlantı dizesi (ayrı veritabanı) desteği, şube (branch) ve organizasyon birimi kırılımı.
Bildirim ve entegrasyon SMS, e-posta, WhatsApp, Telegram, Rocket.Chat, masaüstü ve UI kanallarına kural tabanlı bildirim.
Raporlama DevExpress Report Designer/Viewer + ListForm'dan otomatik üretilen dinamik raporlar.
Arka plan işleri Hangfire + ABP Background Worker; zamanlanmış SQL, mail kuyruğu, yedekleme, oturum temizliği.
Gerçek zamanlı SignalR tabanlı messenger ve video oda (WebRTC/TURN) altyapısı.
Intranet & Forum Duyuru, anket, sosyal duvar, etkinlik; kategori/konu/gönderi tabanlı forum.
Public web sitesi Menüsü ve sayfaları veritabanından yönetilen ziyaretçi sitesi: sayfalar Custom Component, menü Pub modülü (bkz. §10.4). Ürün/ödeme ve demo akışları fiziksel sayfalardır.
PWA Service worker ile offline kabuk, kontrollü sürüm güncellemesi ve changelog bildirimi.

2. Mimari Genel Bakış

┌──────────────────────────────────────────────────────────────────────┐
│  UI  —  React 19 + Vite + TypeScript + DevExtreme + Tailwind (PWA)   │
│  ├─ DynamicRouter        (route kayıtlarından React Router üretimi)  │
│  ├─ ListForm runtime     (Grid / Pivot / Tree / Chart / Gantt / ...) │
│  ├─ Form runtime         (DevExtreme Form, metadata ile alan üretimi)│
│  ├─ Developer Kit UI     (SQL, endpoint, component, dynamic service) │
│  └─ easy-peasy store + React Query + SignalR                        │
└───────────────┬──────────────────────────────────────────────────────┘
                │ HTTPS / OAuth2 (OpenIddict) / SignalR
┌───────────────▼──────────────────────────────────────────────────────┐
│  API  —  ASP.NET Core (.NET 10) + ABP Framework 10                   │
│  ├─ HttpApi.Host   : Swagger, OpenIddict, Hangfire, SignalR Hub,     │
│  │                   DevExpress Reporting, Setup/Migration akışı     │
│  ├─ Application    : ListForms, DeveloperKit, Identity, Tenants,     │
│  │                   Menu, Routes, DataSource, FileManagement, ...   │
│  ├─ Domain         : Entity'ler, dinamik veri erişimi, seed'ler      │
│  └─ EF Core        : Migration'lar, dinamik DbContext, tenant DB     │
│                                                                      │
│  Modüller: Languages · Settings · Notifications · MailQueue ·        │
│            Sender · SqlQueryManager                                  │
└───────────────┬──────────────────────────────────────────────────────┘
                │
┌───────────────▼──────────────────────────────────────────────────────┐
│  Veri ve Altyapı                                                     │
│  SQL Server / PostgreSQL · Redis (cache + SignalR backplane) ·       │
│  Hangfire (job storage) · Dosya sistemi tabanlı BLOB/CDN ·           │
│  coturn (TURN) · nginx · Elasticsearch (opsiyonel)                   │
└──────────────────────────────────────────────────────────────────────┘

Akışın özeti: Kullanıcı giriş yapar → ABP application-configuration yanıtına PlatformApplicationConfigurationContributor menü, route ve yetkileri ekler → DynamicRouter route kayıtlarını React bileşenlerine bağlar → /admin/list/{ListFormCode} gibi bir ekran açıldığında ListFormsAppService metadata'yı, ListFormDataAppService ise veriyi döner → DevExtreme bileşenleri bu metadata'ya göre kendini kurar.


3. Teknoloji Yığını

Backend

Bileşen Sürüm / Notlar
.NET net10.0 (SDK 10.0.300, global.json ile sabit)
ABP Framework 10.0.0 (Identity, TenantManagement, PermissionManagement, AuditLogging, OpenIddict, BlobStoring, BackgroundWorkers)
Veritabanı SQL Server 2022 veya PostgreSQL 17 (PlatformConsts.DefaultDatabaseProvider)
ORM Entity Framework Core
Kimlik OpenIddict (password + refresh token, /connect/token)
Cache Redis (opsiyonel; Redis:IsEnabled)
Job Hangfire (default, platform kuyrukları) + ABP Background Workers
Raporlama DevExpress Reporting (Web Document Viewer + Report Designer)
Log Serilog
Dinamik derleme Roslyn (DynamicServiceCompiler)
Şablon Scriban (mail şablonları), MailKit/MimeKit
Gerçek zamanlı SignalR (Redis backplane destekli)

Frontend

Bileşen Sürüm / Notlar
React 19.2
Build Vite 8 + TypeScript 5.9
UI kütüphaneleri DevExtreme 26.1 (React), Tailwind CSS 3.4, kendi components/ui tasarım sistemi
Routing react-router-dom 7 (dinamik route üretimi)
State easy-peasy (global store) + @tanstack/react-query
Form Formik + Yup, DevExtreme Form
Kod editörü Monaco Editor
Runtime derleme @babel/standalone (Custom Component)
Takvim/Gantt DevExtreme Scheduler/Gantt
Gerçek zamanlı @microsoft/signalr
PWA vite-plugin-pwa (Workbox)
Node 24.13 / npm 11 (volta ile sabitlenmiş)

4. Depo Yapısı

sozsoft-platform/
├── api/                                   # .NET çözümü (Sozsoft.Platform.sln)
│   ├── src/
│   │   ├── Sozsoft.Platform.Domain.Shared/    # Sabitler, enum'lar, permission kodları (AppCodes)
│   │   ├── Sozsoft.Platform.Domain/           # Entity'ler, dinamik veri erişimi, repository'ler
│   │   ├── Sozsoft.Platform.Application.Contracts/  # DTO'lar, servis arayüzleri, permission tanımları
│   │   ├── Sozsoft.Platform.Application/      # Uygulama servisleri (iş mantığı)
│   │   ├── Sozsoft.Platform.EntityFrameworkCore/    # DbContext, migration'lar, dinamik veri katmanı
│   │   ├── Sozsoft.Platform.HttpApi/          # Controller'lar (conventional)
│   │   ├── Sozsoft.Platform.HttpApi.Host/     # Host: Swagger, auth, Hangfire, SignalR, raporlama
│   │   └── Sozsoft.Platform.DbMigrator/       # Migration + seed uygulaması
│   ├── modules/                           # Bağımsız ABP modülleri
│   │   ├── Sozsoft.Languages/                 # Dil, dil anahtarı, çeviri metinleri
│   │   ├── Sozsoft.Settings/                  # Ayar tanımları ve ayar UI'ı
│   │   ├── Sozsoft.Notifications/             # Bildirim tipleri, kuralları, kullanıcı bildirimleri
│   │   ├── Sozsoft.MailQueue/                 # Şablonlu mail kuyruğu ve ekleri
│   │   ├── Sozsoft.Sender/                    # Mail (SES), SMS, WhatsApp, Rocket.Chat göndericileri
│   │   ├── Sozsoft.SqlQueryManager/           # SQL nesne yönetimi ve sorgu çalıştırma
│   │   └── Sozsoft.Mcp/                       # MCP sunucusu: /mcp ucu, şema keşfi, sorgu ve geliştirme araçları
│   └── test/                              # Test projeleri
├── ui/                                    # React uygulaması
│   └── src/
│       ├── routes/        # dynamicRouter, dynamicRouteLoader, route.constant
│       ├── views/         # list, form, admin, developerKit, public, intranet, forum, report, auth, setup
│       ├── components/    # ui (tasarım sistemi), template (layout), importManager, visualDesigner, componentEditor
│       ├── services/      # API istemcileri (her modül için ayrı servis dosyası)
│       ├── store/         # easy-peasy modelleri (auth, theme, locale, abpConfig, admin)
│       ├── contexts/      # ComponentContext (runtime bileşenler), Dialog, Scroll
│       └── utils/         # editorScriptRuntime, workflow, hooks, hoc, biçimlendirme yardımcıları
├── configs/
│   ├── docker/            # Yerel geliştirme için veri katmanı compose dosyaları ve scriptler
│   ├── deployment/        # Dev/Prod compose dosyaları, nginx/redis/turn konfigürasyonu, deploy scriptleri
│   ├── seeds/             # Runtime'da düzenlenebilen seed'ler; host ve tenant'lar için tek klasör
│   │                      #   {crud,wizard,data,docs/modules}
│   │                      #   {sql|postgres}/{object,execute}
│   └── ai/                # AI entegrasyonu (n8n akış tanımları, Ollama model kurulum notları)
├── .github/instructions/  # ai.instructions.md      (bağlayıcı platform kuralları)
│                          # lowcode.instructions.md (artefakt üretim referansı: şemalar, örnekler)
│                          # dotnet.instructions.md  (api/ için bağlayıcı .NET/ABP kod standardı)
│                          # list.instructions.md    (modül/liste ekleme prosedürü)
├── CLAUDE.md              # Claude Code için operasyon kuralları
└── README.md

5. Hızlı Başlangıç

5.1. Önkoşullar

  • .NET SDK 10.0.300+
  • Node.js 24.x ve npm 11.x (Volta önerilir)
  • Docker Desktop (veritabanı ve Redis için)
  • DevExpress lisansı (api/DevExpress_License.txt) ve DevExtreme npm erişimi

5.2. Veri katmanını ayağa kaldırma

# SQL Server + Redis
.\configs\docker\start_sql.bat

# veya PostgreSQL + Redis
.\configs\docker\start_postgres.bat

Compose dosyaları configs/docker/docker-compose-data.yml içindedir; sql ve postgres profilleri ile hangi veritabanının kalkacağı seçilir. Redis her iki profilde de çalışır.

5.3. Backend

cd api
dotnet restore
dotnet run --project src/Sozsoft.Platform.HttpApi.Host

Uygulama https://localhost:44344 adresinde açılır. Swagger UI kök adreste yayınlanır.

Veritabanı ilk kurulum: Veritabanı yoksa host, tam ABP yığını yerine minimal bir "setup" uygulaması ile açılır (DbStartup/SetupAppRunner). Bu durumda:

  • GET /api/setup/application-status — veritabanının hazır olup olmadığını döner (anonim).
  • UI tarafında /setup route'u (views/setup/DatabaseSetup.tsx) migration'ı tetikleyen ekranı gösterir.
  • Migration için Setup:MigrationPassword yapılandırması veya App.Setup.Migrate yetkisi gerekir.
  • Ekran DbMigrator'ü SetupMigrationRunner ile çalıştırır. Setup:MigratorPath publish çıktısını gösteriyorsa DLL doğrudan çalışır; kaynak proje klasöründe (geliştirme) bin/Debug altındaki var olan DLL çalışır. Migrate restore/build yapmaz: DLL yoksa hata verir, api/ kaynaklarından eskiyse yalnızca uyarı yazar — kod değişikliğinin migrate'e girmesi için DbMigrator elle derlenir.

Alternatif olarak migration + seed'i doğrudan çalıştırabilirsiniz:

dotnet run --project api/src/Sozsoft.Platform.DbMigrator

DbMigrator; şema migration'larını uygular ve ardından menü, route, permission, dil, host ayarları, ülke/şehir/ilçe verileri ile seed edilmiş ListForm ekranlarını yükler (kodla versiyonlanan sabit seed'ler api/src/Sozsoft.Platform.DbMigrator/Migrations/, runtime'da düzenlenebilen seed'ler configs/seeds/ altındadır — bkz. App:SeedsPath).

Tek tenant seed'i: Tenants ekranındaki bağlantı diyaloğunun Seed sekmesi POST /api/setup/tenants/{id}/seed ucunu çağırır (AbpTenantManagement.Tenants.Update, yalnızca host kullanıcısı). Uç DbMigrator'ü migrate ile aynı kilit ve SSE akışıyla çalıştırır; TenantSeed:TenantId, TenantSeed:AdminEmail, TenantSeed:AdminPassword ortam değişkeniyle geçer (TenantSeedOptions) ve PlatformDbMigrationService yalnızca o tenant'ın şema + seed + execute adımlarını yürütür. Loglar DB Migrate'teki DbMigrateLogPanel ile gösterilir.

configs/seeds tek klasördür; tenant'a özel bir seed kapsamı yoktur. Kendi veritabanı olan tenant'ta ekran tanımları, menüler ve yetki tanımları gibi kiracısız varlıklar host'ta kalır, çünkü ABP'nin EF repository'si IMultiTenant olmayan varlığı her zaman host bağlantısıyla okur. Kiracının veritabanına iki şey yazılır: IMultiTenant varlıklar (kullanıcı, rol, yetki ataması) ve kiracı tablolarının satırları. Bu satırlar Code = tenant adı olan DataSource üzerinden yazılır (DataSourceManager). Tabloların scriptleri ile veri dosyalarının kiracı satırları o veritabanında da gerektiği için host ve kendi veritabanı olan her tenant aynı dosyalarla kurulur. Kiracısız satırlar host'ta zaten olduğundan bu adımda atlanır. Host veritabanını paylaşan (bağlantı cümlesi olmayan) tenant'ta dosyalar yeniden uygulanmaz, çünkü host seed'i onları aynı veritabanına zaten yazmıştır. Karar SeedFileScope'tadır: migrate kararı DataSeedContext'e yazar, dosya okuyan her seeder (SqlDataSeeder, CrudDataSeeder, WizardDataSeeder, ListFormSeedDataApplier, FinalSeeder) aynı kararı uygular. İçerik türüne göre klasörler:

configs/seeds/
  crud/  wizard/  data/  docs/
  sql/      { object/  execute/  host/{ object/  execute/ } }
  postgres/ { object/  execute/  host/{ object/  execute/ } }

Dosyalar ürünün varsayılanıdır ve yalnızca host bağlamında yazılır. Tenant bağlamındaki bir kayıt veritabanında kalır, seed dosyasına yansımaz; yansısaydı bir tenant'ın verisi bütün veritabanlarına yayılırdı. Bunu ListFormSeedDataSynchronizer ve WizardSeedSynchronizer uygular. Wizard, SQL Query Manager, CRUD Endpoint ve Custom Components yetkileri zaten yalnızca host tarafındadır.

Bekleyen wizard kurulumu (Pending) dosyaları uygulayan son veritabanında kapanır. Tam migrate'te bu, kendi veritabanı olan son tenant'tır; öyle bir tenant yoksa host'tur. Host'ta kapansaydı, ekranın eski sürümü kurulu olan tenant ekranı "kurulu" sayıp güncellemezdi. Tek tenant seed'i (Tenants › Seed) bayrağı kapatmaz.

Aktif veritabanı sağlayıcısına göre sql/ (SQL Server) ya da postgres/ okunur. İki klasör birebir eştir: her sql/…/X.sql dosyasının aynı adlı postgres/…/X.sql karşılığı vardır (PG'de şema public; çeviri kuralları lowcode.instructions.md §8.3.1). EF modeli SQL Server sözdizimiyle yazılır, PostgreSQL'de PostgreSqlModelSyntax ile çevrilir (PlatformDbContext.ConfigureProviderSpecificModel); Order.IsActive SQL Server'da lisans bitişinden hesaplanan kolondur, PG'de hesaplanan kolon CURRENT_DATE alamadığı için saklanan kolondur (varsayılan true). Her .sql dosyası amacını belirten bir klasörün altındadır; klasör köklerinde dosya bulunmaz:

Klasör Ne zaman çalışır
{sql|postgres}/object Seed sırasında nesneyi oluşturur/günceller. SqlTableDesigner ve SqlQueryManager, deploy ettiği script'i bu klasöre yazar.
{sql|postgres}/execute Nesneyi oluşturur; ayrıca tüm migration ve seed'ler bittikten sonra FinalSeeder dosya adından türettiği procedure'ü çalıştırır (dosya adı = procedure adı, schema.Procedure.sql de olur).
{sql|postgres}/host/{object|execute} Aynı iki kural, yalnızca host veritabanında. Tenant veritabanında karşılığı olmayan nesnelere dayanan script buraya konur — örneğin HangFire tabloları tenant migrate'inde de yalnızca host'a kurulduğu için Sas_H_BackgroundWorker_JobFlow view'ı buradadır. Sıra: object → host/object → execute → host/execute. SqlTableDesigner ve SqlQueryManager her zaman object/'e yazar; yalnızca host'ta çalışması gereken script File Manager'dan buraya taşınır. Wizard export/import bu klasörü de taşır.
wizard ListForm Wizard'ın ürettiği .json konfigürasyonları; WizardDataSeeder okur.
crud CrudEndpoint konfigürasyonları; CrudDataSeeder dosyaları ICrudEndpointSeedApplier üzerinden uygular. Politika damga karşılaştırmalıdır: eksik endpoint her zaman eklenir, mevcut bir kayıt yalnızca dosyanın GeneratedAt damgası kaydın LastModificationTime/CreationTime değerinden yeniyse güncellenir ya da silinir; aksi halde kayıt korunur ve log'a drift uyarısı düşer. Böylece CRUD Endpoint Manager'dan yapılan bir revizyon depodan gelen eski bir dosya yüzünden migrate sırasında geri alınmaz. Damgası olmayan (eski biçimde yazılmış) dosya hiçbir zaman yeni sayılmaz.
data Ekranlardan girilen listelerin veri aynası: tek dosya ({ListFormCode}.json) ya da satır başına dosya tutan klasör ({ListFormCode}/{anahtar}.json; klasör özyinelemeli taranır ve tamamı tek unit of work'te uygulanır). Biçimi yalnızca ekranın SeedFilePath tanımı belirler; hedefi okuyan/yazan her yol ListFormSeedTarget üzerinden gider ve tanımın dışında kalan dosya uygulanmaz. ListForm.SeedFilePath dolu olan ekranda SeedSyncInsert/Update/Delete bayraklarından işaretli olan işlemler dosyaya yansır (ListFormSeedDataSynchronizer); migrate/seed sırasında bütün contributor'lar bittikten sonra ListFormSeedDataApplier dosyayı geri uygular: anahtarı veritabanında olmayan satırları ekler, var olanlara dokunmaz. Aynı uygulayıcı wizard import'unun kapanışında da (CompleteImport) yalnızca o import'ta yazılan dosyalar için, overwriteExisting açık olarak çalışır — orada var olan satır dosyadaki değerlerle güncellenir.

Klasör kökünde kalmış eski .sql dosyaları geriye dönük uyumluluk için hâlâ işlenir, fakat SqlDataSeeder bunları uyarı ile loglar — ilgili klasöre taşınmaları beklenir.

5.4. Frontend

cd ui
npm install
npm start          # vite dev server (http://localhost:3000)

Visual Designer ve Custom Component altyapısının kullandığı bileşen kataloğu src/components/visualDesigner/generated/componentProps.json dosyasında tutulur ve repoya dahildir. src/components/ui altındaki bileşen prop'ları değiştiğinde bu dosya elle güncellenmelidir.

5.5. İlk giriş

Seed edilen host yöneticisi ile /login üzerinden giriş yapılır (kullanıcı bilgileri PlatformIdentityDataSeeder ile belirlenir). Giriş sonrası /admin/dashboard açılır.

Yöneticinin ilk yetkileri, yöneticinin hiç yetkisi yokken bir kez atanır. Host yöneticisi kimlik yetkileri (rol/kullanıcı) ve Setup.Migrate ile başlar. Kiracı yöneticisi ise host'taki tanım tablosunda (AbpPermissions) kiracı tarafına açık bütün yetkileri alır. Sonradan deploy edilen ekranların yetkileri migrate ile atanmaz.

Kendi veritabanı olan kiracıda yalnızca IMultiTenant varlıklar (kullanıcı, rol, yetki ataması) o veritabanındadır. ListForm, menü ve yetki tanımı gibi kiracısız varlıkları ABP her zaman host'ta okur. Kiracı tablolarının satırları, kiracı adıyla kayıtlı veri kaynağından okunup yazılır (DataSourceManager).


6. Yapılandırma (Configuration)

6.1. Backend (appsettings.json)

Anahtar Açıklama
App:SelfUrl API'nin kendi adresi (OpenIddict authority ile uyumlu olmalı).
App:ClientUrl React uygulamasının kök adresi (mail linkleri bu adresi kullanır).
App:CorsOrigins Virgülle ayrılmış izinli origin listesi; wildcard subdomain destekli (https://*.alan.com).
App:BaseDomain Production'da ana alan adı ve tüm alt alan adları (yalnızca https), geliştirmede dev.{alan} CORS'a açılır. Tek politika PlatformCorsOriginValidator'dadır: origin bu kurala ya da App:CorsOrigins'e uyuyorsa izinlidir; geliştirmede localhost/127.0.0.1/[::1] her zaman açıktır.
App:RedirectAllowedUrls OAuth yönlendirme için izinli adresler.
App:CdnUrl / App:CdnPath Dosya servisinin adresi ve BLOB kök dizini.
App:AttachmentsPath Mail kuyruğu eklerinin yazıldığı dizin.
App:BackupPath Veritabanı yedek dizini (BackupWorker kullanır).
App:Version Uygulama sürümü (UI sürüm kontrolü ile eşleşir).
ConnectionStrings:SqlServer / PostgreSql Host veritabanı bağlantıları.
Redis:IsEnabled / Redis:Configuration Dağıtık cache ve SignalR backplane.
AuthServer:Authority / SwaggerClientId OpenIddict authority ve Swagger istemcisi.
OpenIddict:TokenLifetimes:* Access/refresh token ömrü (dakika).
Setup:MigrationPassword Setup ekranından migration tetiklemek için parola.
StringEncryption:DefaultPassPhrase ABP string şifreleme anahtarı.
Serilog:MinimumLevel Log seviyeleri.

Güvenlik: Depodaki appsettings.json dosyaları geliştirme değerleri içerir. Üretimde bağlantı dizeleri, MigrationPassword, DefaultPassPhrase ve entegrasyon anahtarları ortam değişkeni veya secret yöneticisi ile verilmelidir; repoya yazılmamalıdır.

6.2. Frontend (ui/.env, .env.dev, .env.production)

Değişken Açıklama
VITE_API_URL API kök adresi.
VITE_CDN_URL Dosya/CDN kök adresi.
VITE_REACT_APP_VERSION package.json sürümünden beslenir.
VITE_AI_URL AI asistanının n8n webhook kökü.
VITE_USE_POLLING Dosya izlemede polling (WSL/Docker senaryoları).
VITE_PWA_DEV Geliştirmede service worker'ı açar (varsayılan kapalı).

6.3. Uygulama içi ayarlar

Çalışma zamanı ayarları veritabanında tutulur ve Ayarlar ekranından (/admin/settings) yönetilir. Sozsoft.Settings modülü ayar tanımlarını, SettingUiAppService ise bu tanımların UI gösterimini yönetir. Şifre politikası, oturum kuralları, gönderici kimlik bilgileri (SMS, mail, WhatsApp, Rocket) ve AI bot yapılandırması bu yolla değiştirilir — kod dağıtımı gerekmez.


7. Low-Code Motoru: ListForm Ekosistemi

Platformun kalbi budur. Bir iş ekranı; bir ListForm kaydı, ona bağlı ListFormField kayıtları ve bir menü/route/permission üçlüsünden oluşur.

7.1. Kavramlar

Varlık Rolü
DataSource Hedef veritabanı bağlantısı (MSSQL / PostgreSQL). Ekranlar DataSourceCode ile bağlanır.
ListForm Ekranın tüm davranışı: veri kaynağı, sütun/filtre/sayfalama/düzenleme ayarları, yetkiler, alt formlar, iş akışı, grafik ayarları.
ListFormField Tek bir sütun/alanın davranışı: başlık, tip, genişlik, editör, lookup, doğrulama, biçim, yetki.
ListFormCustomization Kullanıcı bazlı kişiselleştirme: kayıtlı filtre, grid state, sunucu tarafı join/where.
ListFormWorkflow Onay ve koşul düğümlerinden oluşan iş akışı grafiği.
ListFormImport / ImportLog Excel/CSV içe aktarma tanımı ve satır bazlı sonuç günlüğü.

7.2. ListForm anatomisi

Ekran davranışı büyük ölçüde JSON kolonlarında saklanır. Öne çıkanlar:

Alan İşlevi
SelectCommandType + SelectCommand Veri kaynağı türü: Table, View, TableValuedFunction, Query, StoredProcedure.
KeyFieldName / KeyFieldDbSourceType Birincil anahtar alanı ve tipi.
PermissionJson Ekranın create/read/update/delete/export/import/note yetki eşlemesi.
EditingOptionJson / EditingFormJson Düzenleme modu (row/cell/batch/form/popup) ve düzenleme formu düzeni.
FilterRowJson, HeaderFilterJson, FilterPanelJson, SearchPanelJson, GroupPanelJson Filtreleme, arama ve gruplama davranışı.
ColumnOptionJson, PagerOptionJson, SelectionJson, StateStoringJson Sütun, sayfalama, seçim ve durum saklama ayarları. Durum kullanıcı bazlıdır ve grid, pivot, ağaç, gantt, todo ile form sayfasının alan sırasını (Form Düzeni) kapsar.
Insert/Update/DeleteCommand (+ Before / After) CRUD komutları ve öncesi/sonrası çalışacak SQL kancaları.
Insert/Update/DeleteServiceAddress Varsayılan olarak list-form-data/*; özel endpoint'e yönlendirilebilir.
SeedFilePath Doluysa seed sırasında listenin verisi seed klasöründeki bu hedeften geri yüklenir: .json ile biten değer tek dosya (data/{ListFormCode}.json), uzantısız değer satır başına dosya tutan klasördür (data/{ListFormCode}/{anahtar}.json).
SeedSyncInsert / SeedSyncUpdate / SeedSyncDelete Ekranda yapılan hangi işlemin seed dosyasına yansıyacağı; kapalıysa o işlem dosyaya dokunmaz.
SubFormsJson Ana-detay ilişkisi (ParentFieldName → ChildFieldName eşlemesi).
WidgetsJson Ekranın üstünde gösterilecek KPI kartları. Alt form olarak üretilen ekrana (menüsüz ya da menülü) yazılmaz; menüsüz wizard'da deploy boşaltır, alt form sekmesinde çizilmez.
WorkflowJson İş akışı bağlaması.
ExtraFilterJson Ekrana özel ek filtre araç çubuğu.
PivotOptionJson, TreeOptionJson, GanttOptionJson, SchedulerOptionJson, TodoOptionJson Alternatif görünümlerin ayarları.
Grafik alanları (SeriesJson, LegendJson, ArgumentAxisJson, ValueAxisJson, TooltipJson, ZoomAndPanJson, …) DevExtreme Chart yapılandırması.
CustomJsSourcesJson / CustomStyleSourcesJson Ekran yüklendiğinde çalışacak JS/CSS.
IsTenant, IsBranch, IsOrganizationUnit Otomatik tenant/şube/organizasyon birimi filtrelemesi.
UserId, RoleId, CultureName Aynı ekranın kullanıcı, rol veya dil bazlı varyantları.

7.3. Görünüm tipleri

Aynı ListForm tanımı, aynı veri hattı üzerinden birden fazla görünümle sunulabilir:

Grid · Pivot · Tree · Chart · Gantt · Scheduler · CardView · TodoBoard (Kanban) · Widget Group

Kullanıcı görünümler arasında geçiş yapabilir; grid durumu (sütun sırası, genişlik, filtre, gruplama) kullanıcı bazlı saklanır ve sıfırlanabilir.

TodoBoard artık bir Kanban panosudur: kolonlar TodoOptionJson içindeki statusExpr değerlerinden üretilir, kartlar kolonlar arasında sürüklenerek durum değiştirir ve panodan yeni kolon (durum) eklenebilir. Kart içeriği titleExpr, descriptionExpr, dueDateExpr, tagExpr ve atanan kullanıcı alanlarından kurulur.

7.4. Alan (field) davranışı

  • Editör tipleri: dxTextBox, dxTextArea, dxNumberBox, dxDateBox, dxDateRangeBox, dxCalendar, dxCheckBox, dxSwitch, dxRadioGroup, dxSelectBox, dxTagBox, dxLookup, dxDropDownBox, dxGridBox, dxAutocomplete, dxColorBox, dxHtmlEditor, dxSlider, dxRangeSlider — ayrıca görsel yükleme/görüntüleme editörleri.
  • Lookup kaynakları: StaticData, Query, WebService; ebeveyn–çocuk (cascade) desteklidir. Cascade liste popup'ında, satır içi düzenlemede ve form sayfasında (/admin/form/..., FormDevExpress) aynı kuralla çalışır: alan değişince ebeveyni o alan olan çocuklar ve alanın CascadeEmptyFields listesi boşaltılır. Lookup'lı seçim kutusunda yazarak arama ikisinde de varsayılan açıktır; alanın EditorOptions'ında {"searchEnabled": false} kapatır.
  • Popup ↔ form sayfası eşliği: EditorOptions varsayılanları, butonlar, EditorScript, cascade ve platform editörleri (TagBox, GridBox, görsel) iki yerde ortak koddan gelir; tablo lowcode.instructions.md §4.3.4.
  • Doğrulama: ValidationRuleJson ile required/range/pattern/custom kuralları. Kurallar liste popup'ında ve form sayfasında (/admin/form/...) aynı uygulanır; maske (mask) EditorOptions ile verilir.
  • Editör script: EditorScript alanı ile alan değişiminde çalışan JS (örn. tarih farkından gün hesaplama, miktar × birim fiyat toplamı). Çalıştırma utils/editorScriptRuntime.ts üzerinden yapılır.
  • Editör seçenekleri: EditorOptions alanı DevExtreme editörüne geçirilen JSON'dur (format, maske, placeholder, yükseklik, buton görünürlüğü, platform editörlerinin tipli ayarları). Elle JSON yazmak zorunlu değildir; bkz. 7.4.1.
  • Koşullu biçimlendirme: ColumnStylingJson, ColumnCssClass/ColumnCssValue.
  • Alan bazlı yetki: PermissionJson ile sütun düzeyinde okuma/yazma/dışa aktarma kontrolü.

7.4.1. Script Builder ve Editor Options Builder

EditorScript ve EditorOptions alanları hem görsel olarak hem de seeder kodundan üretilir; iki tarafın çıktısı birebir aynı olmak zorundadır.

Editor Script Builder (json-row-operations/editor-script/)

  • Kural sözlüğü scriptRecipes.ts içindedir; her tarif (recipe) tek satırlık runtime çağrısı üretir. Gruplar: hesap (calc), veri (data), görünüm (view), bildirim, API.
  • Üretilen script'in ilk satırındaki // @builder {...} başlığı kuralları saklar. Dialog script'i regex ile çözmeye çalışmaz, bu başlıktan geri okur; başlık bozulursa/elle düzenlenirse script "manuel" sayılır ve kural editörü kapanır.
  • Kural tetikleyicileri: change (varsayılan), open (form açılışında — varsayılan değer üretmek için, script başına // @runOnOpen direktifi eklenir), both.
  • Koşullar and/or ile birleşir: equals, notEquals, contains, empty, notEmpty, greaterThan, lessThan, isTrue, isFalse, always.
  • Dialog'un kendisi ortaktır (components/scriptBuilder/ScriptBuilderDialog.tsx + IntelliSense); ListForm lehçesi formScriptDialect.ts, Visual Designer lehçesi designerScriptDialect.ts ile aynı sözleşmeye bağlanır.

Seeder tarafı (C#) — Sozsoft.Platform.Domain.Shared/Editors/

scriptRecipes.ts ve editorOptions sözlüğünün C# portudur. Seeder ile basılan script/JSON, dialogda açılıp görsel olarak düzenlenebilir kalır. Bir tarif TypeScript tarafında değişirse buradaki karşılığı da güncellenmelidir.

Tip Kullanım
EditorScript Multiply, Subtract, Percent, Sum, Formula, Today, Days, Hours, Copy, SetValue, Clear, ReadOnly, Notify, Ask, OpenUrl, ApiToField, Location, Custom → EditorScript.Build(rules)
EditorScriptRule .When(...) / .WhenAny(...) koşulları, .OnChange() / .OnOpen() / .OnOpenAndChange() tetikleyicisi; string'e örtük dönüşür
EditorScriptCondition Is, IsNot, Contains, IsEmpty, IsNotEmpty, GreaterThan, LessThan, IsTrue, IsFalse, Always
EditorOptions Hazır başlangıçlar: Disabled(), ReadOnly(), ShowClearButton(), Multiline(h), Number(precision), Percent(), Date(), DateTime(), Time(interval), Phone(), Slider(), ImageUpload(), Html(h)
EditorOptionsBuilder Akıcı ekleme: .Placeholder(), .MaxLength(), .Height(), .Width(), .Format(), .Mask(), .Flag(), .Text(), .Number(), .Json(); string'e örtük dönüşür
EditorOptions = EditorOptions.Number(4).ShowClearButton(),
EditorOptions = EditorOptions.Multiline(60).Placeholder("Açıklama"),
EditorScript  = EditorScript.Build(
    EditorScript.Multiply("Quantity", "UnitPrice", "Total"),
    EditorScript.Percent("Total", "VatRate", "VatAmount", mode: EditorScriptPercentMode.Add)
        .When(EditorScriptCondition.IsNotEmpty("VatRate"))),

Editor Options Builder (json-row-operations/editor-options/)

  • UI tamamen optionSpecs.ts veri sözlüğünden üretilir; yeni bir ayar eklemek için oraya tek satır yazmak yeterlidir. Her ayar path (örn. format.precision), tip (boolean üç durumlu / number / text / select / size / stringList / json), grup ve hangi editörlerde anlamlı olduğu bilgisini taşır.
  • platform: true işaretli ayarlar backend'in tipli DTO'ya (GridBoxOptionsDto, TagBoxOptionsDto, ImageUploadOptionsDto) deserialize ettiği alanlardır; yanlış tipte yazılırsa sessizce yok sayılır.
  • presets.ts hazır kalıplar sunar (HTML editör tam araç çubuğu, telefon maskesi, tarih/saat…); kalıp mevcut JSON ile birleştirilir, diğer ayarları silmez.

7.5. İş akışı ve onay

FormTabWorkflow ve views/admin/listForm/workflow/WorkflowDesigner.tsx üzerinden görsel tasarlanır. Düğüm türleri koşul karşılaştırması (CompareColumn/CompareOperator/CompareValue) ve onay adımlarıdır; her adım için NextOnTrue, NextOnFalse, NextOnApprove, NextOnReject geçişleri tanımlanır. Onaycı birden fazla kişi olabilir. Formlar içinde iş akışına bağlı özel butonlar gösterilebilir.

7.6. Yeni ekran ekleme akışı (Wizard)

/admin/listform/wizard altındaki sihirbaz, aşağıdaki artefaktları tek akışta üretir:

  1. ListForm kaydı (kod, ad, başlık, veri kaynağı, select komutu, anahtar alan)
  2. ListFormField kümesi (sütunlar, editörler, lookup'lar, doğrulamalar)
  3. Route kaydı (key, path, componentPath, routeType, authority)
  4. Menu kaydı (ParentCode, Code, DisplayName, Url, Icon, RequiredPermissionName, Order)
  5. Permission kayıtları (.Default, .Create, .Update, .Delete, .Export, .Import, .Note)
  6. Dil anahtarları (menü, başlık, açıklama; EN + TR)
  7. Gerekiyorsa ayar/entegrasyon bağımlılıkları
  8. Seed dosyası — configs/seeds/wizard/{Ad}.json (wizard cevapları + üretilen ListForm/Fields sözleşmesi)
  9. Doğrulama ve geri alma notları

Adım bileşenleri. Adımlar numaralı dosya adları yerine işlevleriyle adlandırılır ve React.lazy ile yüklenir; aynı anda yalnızca görünen adım indirilir, sihirbazın açılış maliyeti adım sayısından bağımsızdır.

Adım Dosya İçerik
Menü ve kimlik WizardStepMenu.tsx Wizard adı, ListForm kodu, menü ağacı/üst menü, ikon, sıra, izin grubu, dil metinleri
Veri ayarları WizardStepDataSettings.tsx Veri kaynağı, select komutu tipi, anahtar alan, CRUD endpoint diyaloğu
Alanlar WizardStepFields.tsx Sütun grupları, editör tipleri, lookup, doğrulama
Alt formlar WizardStepSubForms.tsx Ana–detay ilişki eşlemesi
Widget'lar WizardStepWidgets.tsx KPI kartları
İş akışı WizardStepWorkflow.tsx Onay/koşul grafiği
Düzen adımları WizardStepTodoLayout · WizardStepTreeLayout · WizardStepGanttLayout · WizardStepSchedulerLayout Yalnızca ilgili görünüm açıksa görünür
Bileşen WizardStepComponent.tsx · WizardStepCustomComponent.tsx Custom yolunda bağlanacak bileşen
Yayınlama WizardStepDeploy.tsx Özet, doğrulama ve seed dosyalarının üretimi; ardından Wizard Manager'a dönülür

Menü ağacı yardımcıları (menuTree.ts) adım bileşenlerinden ayrı bir modüldedir; böylece Wizard ve SqlTableDesignerDialog bu fonksiyonları kullanırken adım bileşenlerini pakete çekmez.

Seed dosyası ekranın kendisini taşır. Dosya Wizard bölümünün yanında ekranın GridOptionsEditDto sözleşmesini (ListForm) ve alanların ColumnFormatEditDto sözleşmesini (Fields) tutar; WizardDataSeeder ekranı bu iki bölümden kurar. Deploy veritabanına yazmaz: wizard dosyasının yanında menü, modül, izin grubu, yetki ve dil metinleri platform seed dosyalarına (data/App.Menus.{Menu,Module}.json, data/Abp.Identity.Permission{Groups,s}.json, data/App.Languages.LanguageText.json) satır olarak yazılır (WizardSeedRowStore) ve hepsi DB Migrate ile kurulur. Deploy/import dosyaya Pending yazar; Wizard Manager dosyayı "Migrate bekliyor" rozetiyle gösterir, migrate bitince liste ve menü yenilenir. Yetkiler hiçbir role otomatik atanmaz. Böylece Wizard ile ListForm detayı aynı veri yapısını konuşur ve tasarımcıdaki değişiklikler wizard'a geri yazılabilir. Wizard bölümü yalnızca ekrandan üretilemeyen bilgileri (menü, yetki, dil metinleri, bileşen yolu, alan başlıklarının dil karşılıkları) taşır; ListForm/Fields içinde karşılığı olan alanlar dosyaya yazılmaz, okuma sırasında geri doldurulur. Kalanlar dosyada Menu, Language ve Groups başlıkları altında toplanır (WizardSeedFileDto.ToSeedJson / FromSeedJson). Dosya adı {Ad}.json'dır (zaman damgası yok); uygulama sırası Wizard.SeededAt damgasından gelir. MCP deploy_wizard dosyaya etkin talebi DevRequest (Id, Title) olarak yazar; elle yapılan kayıt bu bağı korur, talepsiz wizard'da bölüm yoktur.

İki yol: ComponentKind. ListFormWizardDto.ComponentKind sihirbazın yol ayrımıdır ve seed dosyasında ilk alandır. Varsayılan List olduğu için bu alanı taşımayan eski seed dosyaları eskisi gibi çalışır.

ComponentKind Üretilen Menü URL'i Yetkiler
List ListForm + ListFormField (+ iş akışı) + Menu + Permission + dil anahtarları (rota üretilmez; genel /admin/list/{ListFormCode} rotası kullanılır) /admin/list/{ListFormCode} .Create, .Update, .Delete, .Export, .Import, .Note
Custom Menü + Permission + dil anahtarları; ekran seçilen Custom Component'tir Component'in RoutePath değeri Yalnızca .Create, .Update, .Delete (Export/Import/Note ListForm'a özgüdür)

Menüsüz wizard (CreateMenu = false). Menü (ve gerekiyorsa üst menü) kaydı hiç üretilmez; ListForm, permission ve dil anahtarları her iki durumda da üretilir. Menüsüz wizard'lar başka bir ekranda SubGrid/parça olarak kullanılmak üzere tanımlanır. Alan varsayılanı true olduğundan bu alanı taşımayan eski seed dosyaları etkilenmez.

İzin grubu seçimi. Menü adımında izin grubu bir seçim listesinden gelir (ListFormWizardAppService.GetPermissionGroups, /api/app/list-form-wizard/permission-groups). Görünen adlar, grup adıyla aynı olan dil anahtarının metinlerinden okunur; seçim yapıldığında PermissionGroupEn/Tr forma doldurulur. Yeni bir grup tanımlanırsa grup adıyla aynı dil anahtarına bu metinler yazılır; mevcut grup seçilip alanlar boş bırakılırsa sunucu veritabanındaki değeri geri yazar. Böylece seed dosyası her durumda iki dilli görünen adı taşır.

Dinamik liste ekranlarının route deseni /admin/list/{ListFormCode} biçimindedir; form görünümleri /admin/form/{listFormCode}/:id, grafik /admin/chart/{listFormCode}, pivot /admin/pivot/{listFormCode} yollarını kullanır.

Var olan bir ekranı düzenlemek için /admin/listform/edit/{listFormCode} — tüm JSON yapılandırması sekmeli bir editörde yönetilir (Veritabanı, Sütunlar, Filtreler, Düzenleme, Yetkiler, Alt Form, Pivot, Tree, Gantt, Scheduler, Todo, Widget, Workflow, Durum, Grafik sekmeleri). Sütunlar sekmesinde alanlar sürüklenerek sıralanır (form-fields/fieldOrder.ts): liste görünümünde ListOrderNo bütün alanlar arasında, form görünümünde EditOrderNo yalnızca aynı form grubundaki alanlar arasında yeniden 1'den dağıtılır; grup değiştirmek Options sekmesindedir. Kültür/rol/kullanıcı kayıtları kendi sırasını taşır, yalnızca değeri değişen alanlar kaydedilir. Veritabanı sekmesindeki varsayılan değer satırlarında alan adı Select Command kolonlarından seçilir ve FieldDbType kolon tipinden dolar; liste kısıtlamaz — sorgu tipli kaynağın kolonları katalogdan okunamadığı için ad elle de yazılabilir.

Wizard File Manager (/admin/listform/wizardManager; sihirbazın kendisi /admin/listform/wizard) üretilmiş seed dosyalarını yönetir: liste/kart görünümü arasında geçiş (tercih diğer listelerle aynı yerde, admin.lists.states altında saklanır), arama, ağacın tümünü açma/daraltma (arama sırasında gruplar zaten açıktır), düzenleme (dosyanın yerine geçen yeni bir çalıştırma), silme, veritabanı migrate tetikleme, dışa aktarma ve içe aktarma. Liste, wizard'ın ilk deploy damgasına (Wizard.SeededAt) göre tersine sıralıdır — en son üretilen ekran en üstte durur; damgası okunamayan dosyalar sona düşer. Silme, bağlı wizard'ı olan dosyada reddedilir; onay diyaloğu önce DeleteAnalysis ile düşürülebilecek tabloyu sorar, sonuç her kaydın akıbetiyle (Deleted / Kept + kullananlar / NotFound) rapor olarak gösterilir (WizardRecordCleaner, WizardTableCleaner; lowcode.instructions.md §4.5). Butonlar App.Listforms.Wizard.* yetkileriyle gizlenir; asıl kontrol ListFormWizardAppService üzerindedir.

7.7. İçe/dışa aktarma

Veri (ekran içi)

  • Dışa aktarma: xlsx, csv, pdf (grid durumuna ve görünür sütunlara saygı duyar).
  • İçe aktarma: components/importManager altındaki dosya yükleme → önizleme → ilerleme → sonuç akışı; sonuçlar ListFormImportLog üzerinde satır bazında raporlanır. Şablon dosyası ekran tanımından üretilir.

Ekran tanımı (wizard seed paketi)

Bir ekranın tanımı, bağımlılıklarıyla birlikte tek bir zip olarak taşınır. Zip içindeki yollar seed klasörünün kökü baz alınarak yazılır; arşiv başka bir ortamda aynı klasör düzenine doğrudan açılabilir. Eski arşivlerdeki host/ öneki import'ta yok sayılır.

Toplama özyinelemelidir (WizardExportCollector): alt formların wizard dosyaları kuyruğa eklenir ve onların bağımlılıkları da aynı kurallarla toplanır. Böylece zip tek başına çalışır. Talep export'u (api/app/list-form-wizard/export-dev-request{,-analysis}) bir geliştirme talebinin DevRequest bağı taşıyan bütün wizard'larını aynı toplayıcıda tek kapanışa koyar; paylaşılan dosyalar arşive bir kez, birleşik satırlarla girer ve import arşivi tek oturumda uygular.

Yol İçerik
wizard/{dosya}.json Wizard tanımının kendisi ve SubFormsDto[].Code ile bağlı alt form ekranlarının wizard dosyaları (özyinelemeli)
data/App.DeveloperKit.CustomComponents/{Id}.json Custom yolunda bağlı component ve bağımlılıkları — yalnızca bu bileşenlerin satır dosyaları
data/{ListFormCode}.json ya da data/{ListFormCode}/{anahtar}.json Ekranın SeedFilePath değeri doluysa kendi veri aynası (dosya ya da klasör)
crud/{entity}.json Component'lerin/nesnenin kullandığı CRUD endpoint tanımları
{sql|postgres}/[host/]{object|execute}/{nesne}.sql SelectCommand'a karşılık gelen SQL nesnesi, lookup sorgularının (LookupDto.LookupQuery) FROM/JOIN ile işaret ettiği nesneler ve Custom yolunda CRUD endpoint'lerinin dayandığı tablolar

Script dosyaları sabit bir sağlayıcı/klasör kombinasyonundan tahmin edilmez: seed kökündeki script klasörleri (sql/object, sql/host/execute, postgres/execute …) diskten taranır ve nesne adıyla eşleşen her dosya arşive girer. Yeni bir sağlayıcı ya da script klasörü eklendiğinde toplayıcı değişmez.

Script bulunamaması uyarı üretmez: her tablo seed ile yönetilmez — ABP'nin kendi tabloları (AbpUsers, AbpRoles …) EF migration'larıyla kurulur ve seed script'leri yoktur. "Bu tablo hedefte var mı" sorusunun tek doğru cevabı hedef veritabanındadır; kararı import analizi verir, export varsayımda bulunmaz.

Çözülemeyen bir kayıtlı referans (seed dosyasında adı geçen ama karşılığı bulunmayan alt form, SeedFilePath veri aynası, DataSources[].SeedFile CRUD dosyası, custom component satırı) indirmeyi engellemez ama sessiz de kalmaz: ExportAnalysis ucu indirmeden önce arşivin tam içeriğini ve eksikleri döndürür, Wizard File Manager eksik varsa önce bu listeyi gösterir.

İçe aktarma dört adımlı bir sihirbazdır (WizardImportDialog, Steps bileşeni):

Adım İçerik Geçiş koşulu
Dosyalar Zip girdileri ve durumları; çakışanlar Monaco DiffEditor ile çözülür Çözülmemiş çakışma kalmamalı
Kontroller Analiz bulguları ve zip'te atlanan girdiler. Engelleyiciler iki başlıkta ayrılır — arşiv eksik ya da bozuk (burada giderilemez, kaynak ortamda export tekrarlanmalı; düzeltme düğmesi yoktur) ve ortamda giderilebilir. Tabloya dokunan bulgularda mevcut SqlTableDesignerDialog açılır — şema farkında Tabloyu düzenle (düzenleme modu), eksik tabloda Tabloyu oluştur (oluşturma modu, ad ön dolu); deploy sonrası analiz kendiliğinden yenilenir (önceki oturum bırakılır) Engelleyici (Error) bulgu kalmamalı
Yapılacaklar Onaylandığında ne olacağının tam listesi: yazılacak dosyalar, çalışacak SQL script'leri, eklenecek kolonlar (cümleleriyle), uygulanacak CRUD ve veri dosyaları, kurulacak ekranlar. Sıra sunucudaki kapanış sırasıyla aynıdır Onay
Uygulama Dosya dosya yazım, kapanış adımlarının sonucu —

Geriye dönüş her zaman serbesttir; ileri atlama bağımlılık koşullarına bağlıdır. Adım başlıklarına tıklanarak da gezinilebilir.

İçe aktarma toplu (batch) bir işlemdir: engelleme oturum genelindedir, dosya bazlı değildir. Bir tek engelleyici bulgu tüm zip'i durdurur ve o dosyanın işaretini kaldırarak aşılamaz — yarım kurulum üretmemenin tek güvenli yolu budur. Karşılığında, tabloya dair her engel diyalogdan çıkmadan giderilebilir (tablo tasarımcısı → deploy → analiz yenilenir). Tabloyla ilgisi olmayan engeller (eksik alt form, eksik custom component bağımlılığı, bozuk JSON) zip'in kendisinin eksik olduğunu gösterir; bunlar kaynakta düzeltilip yeniden export edilmelidir.

Adımların altında yatan üç sunucu işlemi şunlardır:

  1. Analiz (AnalyzeImport) — zip, seed kökü altındaki .imports/{importId}/staged klasörüne açılır (nokta ile başladığı için seeder taramalarına girmez) ve her dosya hedefteki karşılığıyla karşılaştırılır: New (hedefte yok), Identical (aynı, sorulmaz), Conflict (farklı). Çakışan dosyalar Monaco DiffEditor ile karşılaştırılıp birleştirilir. data/*.json girdilerinde karşılaştırma satır bazlıdır: hedef dosyadan yalnızca gelen satırların anahtarları süzülür, dosya düzeyindeki GeneratedAt/Order karşılaştırmaya girmez. Böylece aktarılan kaydın dışındaki satırlar fark olarak görünmez; gelen satırların hiçbiri hedefte yoksa dosya var olsa bile durum New (ekleme), varsa ve içerik aynıysa Identical (hiç aktarılmaz), farklıysa Conflict (güncelleme) olur.

  2. Uygulama (ApplyImport) — dosyalar tek tek yazılır, ilerleme satır satır gösterilir. Yazılan her dosyanın önceki hâli .imports/{importId}/backup altına alındığı için hata durumunda RollbackImport ile toplu işlem geri alınabilir.

  3. Kapanış (CompleteImport) — oturum kapatılmadan önce zip'in getirdiği dosyalar bağımlılık sırasıyla veritabanına uygulanır. Sıra bilinçli ve sabittir: (a) {sql|postgres}/{object,execute}/*.sql scriptleri çalıştırılır (object önce) — tablo yoksa burada oluşur, aksi halde sonraki iki adım eksik tablo yüzünden baştan düşerdi; (b) hedefte eksik olan kolonlar eklenir (aşağıya bakın); (c) crud/*.json dosyaları ICrudEndpointSeedApplier ile uygulanır; (d) data/*.json dosyaları IListFormSeedDataApplier.ApplyFilesAsync(..., overwriteExisting: true) ile veritabanına uygulanır: tabloda olmayan satır eklenir, var olan satır dosyadaki değerlerle güncellenir (migrate/seed akışından farkı budur — orada dosya bayat olabileceği için canlı kayda dokunulmaz). Kayıt daha önce soft delete edilmişse silinme izleri de temizlenir (IsDeleted, DeletionTime, DeleterId); aksi halde satır tabloda durur ama ekranın varsayılan süzgeci ("IsDeleted" = 'false') onu gizlemeye devam ederdi. Hangi kolonların sıfırlanacağı ekranın DeleteCommand'ından okunur, hard delete yapan ekranda hiçbir şey eklenmez. Hedefle zaten aynı olan (Identical) veri dosyaları da uygulanır: yazacak bir şey yoktur ama satırlar veritabanında eksik ya da silinmiş olabilir. Kullanıcının "mevcudu koru" dediği (farklı olup yazılmayan) dosyaya dokunulmaz. Anahtarı olmayan satır güncellenemez, atlanır; eklenen/güncellenen/atlanan satır sayısı dialogda raporlanır. Bu adım RollbackImport kapsamında değildir — geri alma bu adımdan önce yapılır.

  4. Ekran tanımları (wizard/*.json) — ekran import'ta kurulmaz: dosyaya Pending yazılır, Wizard Manager onu "Migrate bekliyor" gösterir ve ekran, deploy'daki gibi, DB Migrate'te WizardDataSeeder tarafından kurulur (hedefte kurulu olsa da dosyadaki hâliyle). Dosya ekranın üretilmiş ListForm/Fields sözleşmesini taşıdığı için bunlar wizard cevaplarından yeniden türetilmez. Wizard'ın menü/yetki/dil satırlarını taşıyan platform dosyaları 4. adımda uygulanmaz; onları da migrate ekler.

Analiz doğrulama da yapar (WizardImportValidator). Zip'in hedef ortamda yarım bir kurulum bırakıp bırakmayacağı dosyalar yazılmadan önce denetlenir; bulgular Error/Warning olarak raporlanır ve en az bir Error varken import başlatılamaz — sunucu oturuma blocked işaret dosyası bırakır, dolayısıyla ApplyImport ucu doğrudan çağrılsa bile reddeder.

Kural Ağırlık
Bozuk JSON (wizard, crud, data) Error
Wizard bölümü ya da adı olmayan wizard dosyası; ListForm bölümü eksik List wizard'ı Error
Ekranın tablosu (SelectCommand, Table/View) hedefte yok ve zip onu oluşturan .sql dosyasını taşımıyor Error
crud/{Entity}.json'un tablosu hedefte yok ve zip'te oluşturan script yok Error
data/{code}.json'un ekranı ne zip'te ne hedefte var Error
Custom component'in Dependencies ya da DataSources[].SeedFile bağımlılığı ne zip'te ne hedefte var Error
Custom wizard'da bileşen adı / rota yolu boş Error
Alt form (SubFormsDto[].Code) ne zip'te ne hedefte var Error
Nesne varlığı sorgulanamadı (bağlantı/izin) ve script de yok Warning
Zip yalnızca karşı sağlayıcının (sql ↔ postgres) scriptlerini taşıyor Warning
Ekranın SeedFilePath veri dosyası ne zip'te ne hedefte var; boş CRUD/veri dosyası Warning
Script'in kurduğu tablo hedefte var ama bir kolonu eksik Warning — kapanışta eklenir
Eksik kolon NOT NULL, varsayılanı yok ve tabloda kayıt var Error (otomatik eklenemez)
Var olan kolonun uzunluk / kesinlik / ondalık / nullability değeri script'ten farklı Error
Hedefte script'te olmayan fazladan kolon var Warning (dokunulmaz)

Eksik tablo ölçütü "veritabanında var" değil, "veritabanında var ya da zip onu oluşturan scripti taşıyor"dur — kapanış scriptleri veri satırlarından önce çalıştırdığı için zip'te scripti olan tablo eksik sayılmaz.

Şema karşılaştırması — tablo var ama yapısı farklıysa

Üretilen tablo script'leri IF OBJECT_ID(...) IS NULL BEGIN CREATE TABLE ... END kalıbıyla sarılıdır: hedefte tablo varsa CREATE bloğu tamamen atlanır. Dolayısıyla eski şemalı bir tabloya yeni bir kolon kendiliğinden gelmez; ekran o kolonu bir alan olarak tanımladığı için açılışta "geçersiz kolon" hatası verir. Bu sessiz uyuşmazlığı analiz adımı yakalar (SqlCreateTableParser + WizardImportValidator): zip'in getirdiği CREATE TABLE bildirimi, hedef tablonun INFORMATION_SCHEMA.COLUMNS üzerinden okunan canlı şemasıyla karşılaştırılır.

Ayrım kayıp riskine göre yapılır:

  • Eksik kolon eklenir. Kolon eklemek kayıpsız bir işlemdir; analiz "şu kolon eklenecek" diye uyarır, çalıştırılacak cümleyi gösterir ve kapanışta (SQL script'lerinden sonra, CRUD ve veriden önce) uygular. Tek istisna: NOT NULL olup varsayılan değeri olmayan bir kolon dolu bir tabloya eklenemez — kolona hangi değerin yazılacağı bir iş kararıdır, bu durumda import durdurulur ve cümle kullanıcıya bırakılır. Tablonun boş olup olmadığı çalışma anında sorgulanır; boş tabloya böyle bir kolon güvenle eklenir.
  • Var olan kolon değiştirilmez. Uzunluk/kesinlik/ondalık/nullability farkında import durur. Daraltma veri keser, tip dönüşümü başarısız olabilir; doğru işlem veriye bakmayı gerektirir. Fark somut olarak bildirilir ("uzunluk hedefte 200, script'te 300"), düzeltmeyi kullanıcı yapar.
  • Hiçbir kolon düşürülmez, hiçbir tablo DROP edilmez. Hedefteki fazla kolon yalnızca bilgilendirme olarak listelenir.

Karşılaştırma tip adına bakmaz. Sağlayıcı kataloğu kendi kanonik adını raporlar (PostgreSQL'de VARCHAR → character varying, TIMESTAMP → timestamp without time zone); script'teki yazılışla metin olarak karşılaştırmak her VARCHAR kolonunda sahte fark üretirdi. Yalnızca her iki sağlayıcıda da aynı anlamı taşıyan ölçüler karşılaştırılır: uzunluk, kesinlik/ondalık, nullability. Script bir tablo kurmuyorsa (procedure, view) ya da CREATE TABLE çözülemiyorsa karşılaştırma sessizce atlanır — tahmin yürütülmez.

data/*.json dosyaları kapsamdaki bütün satırları taşıdığı için üzerine yazılmaz: satırlar anahtar alan üzerinden birleştirilir (gelen satır varsa günceller, yoksa eklenir, hedefteki diğer satırlar korunur). Diff ekranında düzenlenen içerik de aynı sözleşmeyi taşıdığı için elle birleştirme bu davranışı bozmaz.

Güvenlik sınırları: yalnızca wizard, crud, data, sql, postgres kök klasörleri (SQL sağlayıcı klasörlerinin altında yalnızca object ve execute) kabul edilir; dosya başına 5 MB, arşiv başına 50 MB, en çok 500 girdi. Kabul edilmeyen girdiler analiz sonucunda uyarı olarak listelenir. Yetkiler: App.Listforms.Wizard.Export / App.Listforms.Wizard.Import.


8. Developer Kit

Teknik kullanıcılar için /admin/developerkit altında toplanan araçlar:

Araç Route Ne işe yarar
SQL Query Manager /admin/sqlQueryManager Nesne gezgini, Monaco tabanlı SQL editörü, sonuç grid'i, tablo tasarımcısı, view tasarımcısı ve CRUD endpoint yönetimi.
Custom Endpoint /admin/list/App.DeveloperKit.CustomEndpoints SQL veya servis tanımından REST endpoint üretimi; GET/POST/PUT/DELETE için ayrı yetki kapıları (App.DeveloperKit.CustomEndpoints.*) ve kullanıcı/rol/global erişim kuralları.
CRUD Endpoint SQL Query Manager içinde Tablo tanımından tam CRUD endpoint kümesi üretimi. Ayrı ekranı yoktur; bkz. aşağıdaki not.
Dynamic Service /admin/list/App.DeveloperKit.DynamicServices Liste bir ListForm ekranıdır (KPI şeridi, Swagger butonu, kart/ızgara görünümü); kod yazma listeden açılan DynamicServiceEditor diyalogundadır: Monaco editörü, Roslyn ile derleme (TestCompile), yayınlama (Publish) ve çalışan uygulamaya controller olarak kaydetme. Yetkiler: Create/Edit/Delete/Manage/TestCompile/Publish/ViewCode.
Custom Component /admin/list/App.DeveloperKit.CustomComponents React bileşenini veritabanında saklama; @babel/standalone ile tarayıcıda derleyip route'a bağlama. Liste bir ListForm ekranıdır (KPI şeridi, ızgara/kart görünümü, grid'in kendi ekleme/düzenleme formu); satırdaki Design butonu /admin/developerkit/components/design/{id} tasarımcısını, Open URL butonu bileşenin RoutePath'ini açar. Kayıtlar configs/seeds/data/App.DeveloperKit.CustomComponents/{Id}.json klasörüne senkronlanır. Yetkiler App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}.
Visual Designer /admin/developerkit/components/design/:id Custom Component listesindeki Design butonundan açılır; sürükle-bırak kanvas ile bileşen üretimi ve kod üretimi (visualDesigner/codeGenerator.ts).
ListForm /admin/list/App.Listforms.Listform Var olan ekranların ham tanım listesi; buradan /admin/listform/edit/{kod} sekmeli editörüne geçilir.
Wizard Manager /admin/listform/wizardManager Üretilmiş wizard seed dosyalarının listesi/kartı, düzenleme, silme, DB migrate, export/import. Menüsüz alt formlar onları sekme olarak açan ekranın altında iç içe görünür; kendi menüsü olan ekran başka bir ekranın sekmesi olsa da kökte kalır (export kapanışı onu yine taşır). Wizard'ı üreten talep listede görünür ve kaydına bağlanır; talebin bütün wizard'ları tek arşivde export edilir (export-dev-request) ya da bağımlılık sırasıyla tek işlemde silinir (delete-dev-request; alt formlar önce, talebin ekranları birbirini kullanım saymaz, tablolar en sonda — lowcode.instructions.md §4.5). Yetki App.Listforms.Wizard{,.Create,.Update,.Delete,.Export,.Import}.

Bu ekranların artefakt şemaları, enum değerleri ve çalışan örnekleri .github/instructions/lowcode.instructions.md dosyasındadır.

8.1. SQL View Designer

SqlViewDesignerDialog (+ sqlViewDesigner/), SSMS Query Designer düzeninde görsel view oluşturma aracıdır. Üç panel:

  • Diagram (DiagramPane.tsx) — ViewSource kutuları (x/y konumlu, daraltılabilir, yalnızca seçili kolonları gösterebilir) ve aralarındaki JOIN okları. Kaynak bir tablo/view olabileceği gibi CROSS/OUTER APPLY ya da türetilmiş bir alt sorgu da olabilir; alt sorgunun gövdesi olduğu gibi saklanır. JOIN türleri: INNER, LEFT, RIGHT, FULL, CROSS; koşul operatörleri =, <>, >, >=, <, <=.
  • Criteria grid (CriteriaGrid.tsx) — Column / Alias / Output / Group By / Sort / Filter / Or… satırları. Bir satır ya sourceId + columnName referansıdır ya da serbest bir SQL ifadesidir. Group By sütunu GroupBy, Where (satır çıktıya girmez, yalnızca filtre taşır) ve SUM/COUNT/COUNT_DISTINCT/AVG/MIN/MAX değerlerini alır. Filtre hücreleri SSMS'teki gibi serbest yüklemdir (> 100, LIKE '%abc%', IS NULL).
  • T-SQL önizleme — model → SQL üretimi (generateViewSql).

Tasarımcı tek yönlü çalışır: model → T-SQL. Mevcut bir view açılırken parseViewSql ile geri okunmaya çalışılır; üretilen kanonik şekle uymayan bir tanım gelirse dialog ham SQL moduna düşer ve kullanıcı Query Editor'dan devam eder (onOpenInEditor). PostgreSQL bağlantılarında isPostgreSql bayrağı ile söz dizimi buna göre üretilir.

CRUD Endpoint, SQL Query Manager ile birleştirildi. Ayrı /admin/developerkit/endpoints ekranı ve menüsü kaldırıldı. Endpoint üretme, aktif/pasif etme, test etme ve silme işlemleri artık iki yerden yapılır: SQL Query Manager nesne gezgininde tablo satırının CRUD Endpoints aksiyonu (ve seçili tablolar için toolbar'daki toplu üretim butonu), ayrıca ListForm sihirbazının Adım 2 – Veri Kaynağı ekranındaki aynı diyalog. Ayrıca tablo tasarımcısında deploy öncesi bir adım vardır: endpoint üretilip üretilmeyeceği ve hangi operasyonların (GetList/GetById/Create/Update/Delete) aktif başlayacağı seçilir ("tümünü seç" destekli); seçilmeyenler pasif olarak kaydedilir, sonradan diyalogdan açılabilir. Hepsi CrudEndpointDialog bileşenini kullanır. Yetki App.SqlQueryManager altındaki App.SqlQueryManager.CrudEndpoints'tir; izin verilmemişse butonlar görünmez ve crud-endpoint-generate uçlarının tamamı (base CRUD metotları dahil) reddedilir. Üretilen endpoint'ler configs/seeds/crud/{EntityName}.json olarak seed dosyasına yazılır; CrudDataSeeder aynı dosyaları okuyup endpoint'leri geri yükler.

Tablo tasarımı varsayılanı: Yeni tablo oluştururken tenant ve tam denetim (audit) kolonları varsayılan olarak eklenir: Id, TenantId, CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeletionTime, DeleterId. Kullanıcı açıkça istemedikçe bu kolonlar çıkarılmaz.

8.2. Custom Component ve Visual Designer

Saklanan tanım. Bir custom component Name, RoutePath, Code (JSX), Props, Dependencies ve DataSources alanlarından oluşur. Görsel tasarımcı dokümanı iki yerde tutulur: Props.visualDesigner içinde (nodes, canvas, lifecycle, dataSources) ve üretilen kodun ilk satırındaki /*__SOZSOFT_VISUAL_DESIGNER__…__*/ yorumunda. Böylece yalnızca kod elinizdeyse bile tasarımcı dokümanı geri okunabilir. sourceMode alanı visual ya da code olur; kod moduna geçilen bir bileşen kanvasa geri dönmez.

Seed senkronizasyonu. Bileşenlerin seed kaynağı Custom Components ekranının veri klasörüdür: configs/seeds/data/App.DeveloperKit.CustomComponents/{Id}.json — her bileşen kendi dosyasındadır; bileşen kodu satırın içinde durduğu için tek dosya hızla büyürdü. Bileşene yazan iki yol da bu klasöre işlenir (ListFormSeedDataSynchronizer): listeden yapılan ekleme/güncelleme/silme ve Visual Designer'ın kaydı (CustomComponentAppService). Veritabanı sıfırlandığında ListFormSeedDataApplier satırları geri yazar; Code, Props ve DataSources kolonları bu yüzden ekranda gizli alan olarak tanımlıdır. DataSources sütunu tasarımcı dokümanından türetilir (CustomComponentDataSourceResolver): Data sekmesindeki her endpoint method + path ile CRUD endpoint kataloğunda aranır, eşleşenler EntityName + crud/{EntityName}.json referansını taşır, elle yazılmış olanlar listede kalır ama bu alanları boş gelir. Wizard export'u bu referansları izleyerek bağlı crud/*.json dosyalarını zip'e ekler.

Toolbox. Kanvasa bırakılabilecekler beş aileye ayrılır:

Aile İçerik
layout PageContainer, FlexRow (kolon sayısı, ilk kolon genişliği, hizalama, gap, wrap), Spacer
data Form — ASP.NET'in Form + FormView karşılığı; dört CRUD endpoint'ini sahiplenen kapsayıcı
platform ListView, DataGridView, TreeView, GanttView, TodoBoard, CardView, SchedulerView, PivotView, ChartView — hepsi listFormCode ile bir ListForm ekranını gömer
html / ui Ham HTML etiketleri ve components/ui tasarım sistemi bileşenleri (sözleşmeleri metadata'dan okunur)
custom Başka custom component'ler (bağımlılık olarak kaydedilir)

Form bileşeni. selectEndpoint / insertEndpoint / updateEndpoint / deleteEndpoint, keyFieldName, collectionPath, keySource + keyParamName, previewKeyValue, autoLoad, showToolbar, columnCount, gap özellikleriyle yapılandırılır. İçine bırakılan her bileşen Select sonucunun bir kolonuna bağlanır ve Save/Delete üzerinden geri yazar.

Veri bağlama. DesignerBinding bir kaynağa (sourceId) ve yola (path) bağlanır; labelPath / valuePath seçim bileşenleri içindir. columns ile ekranda görünmeyen ek sütunlar da taşınır ve script'ten refs.<ad>.getColumn('sutun') ile okunup başka bir bileşenin değerine ya da Form kaydına yazılır. Filtreler CrudEndpoint GetList sözleşmesine göre üretilir: eq çıplak query parametresi (?RoleId=…), diğerleri son ek taşır (?Name.contains=…). Operatörler: eq, ne, contains, startswith, endswith, gt, gte, lt, lte, in, isnull, notnull.

Yetki modeli. İki katmanlıdır:

  • Node görünürlüğü — designerPermission prop'u dolu olan bir düğüm, yetki verilmemişse render edilmez.
  • Form komutları — her komut (selectPermission, insertPermission, updatePermission, deletePermission) ya serbesttir (boş) ya da bir yetkiye bağlıdır. Otomatik modda temel, Wizard'ın bu bileşen için ürettiği okuma yetkisidir (menü kaydının korunduğu yetkinin aynısı) ve komutlara '' / .Create / .Update / .Delete son ekleri eklenir. Özel modda yetki adı elle yazılır.

Script. Tasarımcı, ListForm Editor Script ile aynı ortak dialog'u kullanır (designerScriptDialect.ts + designerScriptRecipes.ts); tarif grupları bileşen erişimi, API çağrıları, form ve olay/sayfa başlıkları altında toplanır.

Kanvas doğruluğu. Tasarım zamanında görülen, canlıda görülendir; bunu iki mekanizma sağlar. (1) Yerleşim sınıflarının taşınması: kanvas her düğümü seçim/sürükleme için bir sarmalayıcıya alır ve üst öğe grid/flex olduğunda asıl öğe o sarmalayıcı olur. splitLayoutClasses düğümün yerleşim sınıflarını sarmalayıcıya geçirir — col-span/row-span/order/self/flex-1/basis, sabit ve kesirli w-*, kenar boşlukları taşınır; konum sınıfları (absolute, inset-*, z-*, w-full) her ikisinde de kalır. Görünürlük sınıfları da sarmalayıcıya kopyalanır (hidden lg:flex → sarmalayıcıda hidden lg:block): aksi hâlde mobilde gizlenmesi gereken masaüstü şeridi yine bir grid hücresi işgal eder ve komşularını yanlış hücreye iter. fixed kanvasta akışa çevrilir, yoksa düğüm tasarım yüzeyinden taşardı. (2) Cihaz çerçevesi: araç çubuğundan masaüstü/tablet/mobil seçildiğinde kanvas kendi belgesinde çizilir (CanvasFrame), böylece md:/lg: kuralları tarayıcı penceresine değil seçilen genişliğe göre çözülür. Cihaz modunda tasarım yüzeyi kenar boşluğunu bırakır: kanvas o cihazın ekranıdır. Responsive seçimi aynı sayfada, çerçevesiz ve boşluklu çizer. (3) Gezinme kapalıdır: kanvastaki bir bağlantıya tıklamak seçim yapar, sayfayı değiştirmez — tıklama yakalama evresinde durdurulur, react-router'ın Link'i de defaultPrevented görünce gezinmez.

Diğer notlar. PlatformIcon tasarımcı dokümanındaki ikon adını çözer; selectComponents.ts Select.componentAs için saklanan adı (ReactSelect, CreatableSelect, AsyncSelect) gerçek bileşene çevirir — kanvas ve üretilen bileşenin aynı adı aynı şekilde yorumlamasını sağlayan tek nokta budur. Yeni bileşen configs/seeds/data/App.DeveloperKit.CustomComponents/ klasöründe kendi {Id}.json satır dosyası olarak başlar.

Dinamik servis yaşam döngüsü: DynamicServiceCompiler kodu derler → DynamicAssemblyRegistrationService assembly'yi tenant bağlamıyla kaydeder → ActionDescriptorChangeProvider MVC'ye route tablosunun değiştiğini bildirir → DynamicControllerActivator bağımlılıkları enjekte eder. Uygulama yeniden başlatılmaz.


9. Dinamik Menü, Route ve Yetki Modeli

Permission (ABP)  ──┐
                    ├──►  Menu.RequiredPermissionName  ──►  menüde görünürlük
Route.Authority   ──┘                                   └►  PermissionGuard ile erişim
  • Route (Entities/Tenant/Administration/Route.cs): Key, Path, ComponentType, ComponentPath, RouteType (public / protected), Authority[].
  • Menu (Menu.cs): Code, ParentCode, DisplayName, Url, Icon, Order, RequiredPermissionName, Target, IsDisabled, ayrıca UserId / RoleId / CultureName ile kullanıcı, rol ve dil bazlı menü varyantları.
  • Menü yöneticisi (/admin/menuManager): sürükle-bırak ağaç ile menü düzenleme. Kayıtlar App.Menus.Menu ekranıyla aynı seed dosyasına (data/App.Menus.Menu.json, SeedSync* bayraklarına bağlı) yazılır; menü adları App.Languages.LanguageText.json'a.
  • Frontend'de dynamicRoutesContext route'ları çeker, dynamicRouteLoader bunları fiziksel view'lara veya runtime derlenmiş Custom Component'lere eşler, DynamicRouter React Router ağacını kurar; her korumalı route ProtectedRoute + PermissionGuard ile sarılır.
  • Aynı yolu bir fiziksel route ile bir Custom Component paylaşıyorsa Custom Component kazanır; karşılığı olan dosyası bulunmayan rota kaydı hiç yüklenmez. Public site menüsü bu modelin dışındadır: Pub modülü altında tutulur ve uygulama konfigürasyonuyla taşınır (bkz. §10.4).

Modüller ve kök menüler. Kök menüler bir modüle bağlıdır (Menu.ModuleId → data/App.Menus.Module.json): Sas → App.Saas (Saas), Adm → App.Administration, Hr → App.Hr (İK), Pub → App.Public.Header (public site). Tablo ön eki aynı modülden gelir (MenuPrefix: Sas_, Adm_, Hr_; TableNameResolver). Her yetki satırı Modules (pipe ile ayrılmış) değeri taşır; kiracının modül listesi doluysa menü (MenuAppService) ve rol yetki ekranı (RolesPermission.tsx) yalnızca kesişen modüllerin yetkilerini gösterir. Sözleşme: Sas host'ta görünürlüğü, Adm ve Hr kiracıdaki görünürlüğü belirler — Saas/Administration yetkileri Sas|Adm, İK yetkileri Sas|Hr taşır.

İK (Hr) modülü. Menü App.Hr → App.Hr.Definitions (Yetenek Tipleri App.Hr.SkillType, Departmanlar App.Hr.Department, Pozisyonlar App.Hr.JobPosition) ve doğrudan Organizasyon Şeması App.Hr.OrgChart (/admin/organization; menü ve route aynı yetkiyi ister). Yetki grubu App.Hr; ListForm kodu = menü kodu = yetki adı = dil anahtarı. Tablolar Hr_T_SkillType / Hr_T_SkillLevel / Hr_T_Skill / Hr_T_Department / Hr_T_JobPosition (hepsi kiracı, IMultiTenant, Guid anahtar); ekranlar ListFormSeeder_Hr.cs'tedir, entity'ler Entities/Tenant/Hr/Definitions. Departman ve pozisyon şube bazlıdır: BranchId zorunlu kolondur (Guid, NOT NULL), listenin ilk sütunu Şube Adı'dır (LookupQueryValues.TenantBranchValues); departman lookup'ı şubeye göre süzülür (BranchDepartmentValues). Host varsayılan şubesi sabit kimlikle kurulur (PlatformConsts.Branches.BranchIdDefaultValue) ve data/App.Hr.{Department,JobPosition}.json satırları bu kimliğe bağlıdır. Numaratör (App.Definitions.Sequence) Administration › Definitions altındadır. Intranet de İK'dadır: menü App.Hr.Intranet, ekran/yetki kodları App.Hr.Intranet.*, tablolar Hr_T_* (mesajlaşma dahil), entity'ler Entities/Tenant/Hr/Intranet, seed data/App.Hr.Intranet.*.json; pano widget kaydı (Sas_T_IntranetWidget) Saas'ta kalır. Vardiya, çalışma takvimi, olay günlüğü, Eğitimler (App.Hr.Training), Araçlar (App.Hr.Vehicles), disiplin dosyaları ile izin/masraf/yan hak/disiplin cezası türleri wizard ile üretilmiştir — entity yoktur, ekran wizard/{Ad}.json, tablo sql/object/Hr_T_*.sql. İşe Alım (App.Hr.Recruitment) da wizard ile üretilmiştir: kadro talebi (Grid Workflow onayı) → ilan → aday (CV, KVKK açık rızası zorunlu — tabloda CHECK) → başvuru (aşama kanbanı) → mülakat (takvim, kriter ağırlıklı puan) → teklif (Grid Workflow onayı, teklif mektubu DynamicReport). Aday kaynağı, ret sebebi ve değerlendirme kriteri tanımları İK › Tanımlar (App.Hr.Definitions) altındadır. İşe alınan adayın kullanıcı (personel) kaydını BT elle açar; İK aday kaydını UserId ile o kullanıcıya bağlar. Ayrıntı: lowcode-reference.instructions.md §12.4. Performans Yönetimi (App.Hr.Performance) wizard + custom component ile üretilmiştir: İK menüsü şartları ve takibi taşır, çalışana ait listeler menüsüzdür ve Intranet panosundaki widget'lardan açılır; çalışma şekli App.Hr › Performans Yönetimi. İzin Yönetimi (App.Hr.Leave) aynı kalıptadır (talep, Müdür → İK Grid Workflow onayı, İş Kanunu md.53 bakiyesi, rapor ↔ Sağlık Kartı eşlemesi, kullanıcı detayında İzinler sekmesi); çalışma şekli App.Hr › İzin Yönetimi. Puantaj ve Devam Takibi (App.Hr.Attendance) hareketlerden (PDKS ucu, Excel, elle) günlük ve aylık puantaj kurar; izin, rapor, çalışma takvimi ve vardiyadan beslenir, kullanıcı detayında Puantaj sekmesi ve Intranet'te Puantajım widget'ı vardır; çalışma şekli App.Hr › Puantaj ve Devam Takibi.

Yetki kodları PlatformConsts.AppCodes içinde merkezî olarak tanımlıdır ve configs/seeds/data/Abp.Identity.Permissions.json ile seed edilir (gruplar: Abp.Identity.PermissionGroups.json — App.Saas (1), App.Administration (2), App.Hr (3)). Ana yetki kökleri: App.Saas, App.Branches, App.Definitions.*, App.Hr.* (Intranet dahil, App.Hr.Intranet.*), App.Restrictions.*, App.Languages.*, App.Listforms.*, App.Notifications.*, App.BackgroundWorkers.*, App.Menus.*, App.DeveloperKit.*, App.SqlQueryManager.*, App.Orders.*, App.BlogManagement.*, App.IdentityManagement.*, App.Reports.*, App.Administration, App.Setting, App.Setup.Migrate.

Yetki ekranındaki sıralama AbpPermissions.Order ve AbpPermissionGroups.Order sütunlarından gelir (Modules gibi genişletme alanları, PlatformEfCoreEntityExtensionMappings); ABP kayıtları sırasız okuduğu için sıra taşınmazsa ekran her açılışta başka bir dizilim gösterirdi. Sıra kardeşler arasındadır: her üst yetkinin altında 1'den başlar, grubun kök yetkileri de kendi aralarında 1'den. PlatformPermissionAppService sıralama anahtarını kökten yaprağa üst yetki zincirinden kurar, böylece çocuk her zaman ebeveyninin altında kalır. Değerler seed dosyalarındaki Order alanında tutulur ve Permissions / Permission Groups ekranlarından düzenlenebilir. Yetki ağacı menü ağacını izler. Wizard ekranın okuma yetkisini menüde durduğu dalın altına yazar: üst menü zincirindeki (MenuParentPath + MenuParentCode) her seviye, izin grubunun kendisi hariç, aynı kodlu bir ara yetkidir (İnsan Kaynakları › Tanımlamalar › Ekran → App.Hr.Definitions › App.Hr.Doctor). Var olan ara yetki kullanılır, eksik olan menü seviyesinin görünen adıyla wizard'a ait üretilir; başka bir gruptaki aynı adlı yetki atlanır. Kodu bulunamayan bir seviye önce aynı üstün altındaki dallarla adıyla eşleştirilir (dalın DisplayName anahtarı = verilen kod ya da EN/TR adı aynı) ve eşleşen dalın koduna bağlanır; böylece MCP'nin yalnızca yol verdiği deploy'da ikinci bir "Tanımlamalar" menüsü ve ara yetkisi oluşmaz (ListFormWizardAppService.BindExistingMenuParentsAsync). Menüsüz wizard'ın (CreateMenu: false, alt form) okuma yetkisi onu SubForms içinde taşıyan listenin yetkisine bağlanır: ParentName = listenin yetki kodu (App.Hr.HealthCard › App.Hr.HealthCardFile), sırası listenin alt yetkilerinin (Create/Update/…) ardındandır. Alt form listeden önce deploy edildiği için yetkisi önce grubun köküne yazılır; liste deploy edildiğinde alt formun satırı listenin altına taşınır (satır alt formun wizard'ına ait kalır, başka bir listeye bağlanmış alt form taşınmaz). Hiçbir listenin alt formu olmayan menüsüz wizard'ın yetkisi grubun kökünde kalır. Wizard deploy'u yeni grubu ve yeni yetkileri (seed satırı olarak) veritabanı ile seed dosyasındaki kardeşlerin en büyük sırasının bir fazlasıyla, alt yetkileri WizardSeedRowKeys.ChildPermissions dizilimiyle (List: Create/Update/Delete/Export/Import/Note, Custom: Create/Update/Delete) 1'den yazar (ListFormWizardAppService.WritePermissionGroupAsync / WritePermissionRowsAsync).

Aksiyon yetkisi olan alt gruplar (UI tarafındaki karşılıkları constants/permission.constant.ts içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppService'lerdedir):

Grup Alt yetkiler
App.Listforms.Wizard .Create, .Update, .Delete, .Export, .Import
App.DeveloperKit.CustomComponents .Create, .Update, .Delete, .Export, .Import
App.DeveloperKit.CustomEndpoints .Get, .Post, .Put, .Remove (dispatcher üzerinden çağrı kapısı; endpoint bazlı User/Role/Global kuralları bunun üstünde denetlenir)
App.DeveloperKit.DynamicServices .Create, .Edit, .Delete, .Manage, .TestCompile, .Publish, .ViewCode
App.SqlQueryManager .CrudEndpoints
App.Setup.Migrate Migration + seed tetikleme (host tarafı)

Kural: Yetki sözleşmesi olmayan menü/route önerilmez ve eklenmez.


10. Modül Kataloğu

10.1. Backend modülleri (api/modules)

Modül Kapsam
Sozsoft.Languages Dil tanımları, dil anahtarları, çeviri metinleri; veritabanı tabanlı lokalizasyon (DatabaseResourceLocalizer). Çeviri okuma (public sayfalar dahil) ABP'nin application-localization ucundandır ve yetki istemez; dil/anahtar/metin yönetim uçları ile önbellek temizleme App.Languages.Language* / App.Languages.LanguageText* yetkilerine bağlıdır (LanguagesPermissions). Önbellek temizleme yalnızca uygulamanın anahtarlarını siler (DistributedCacheCleaner). DatabaseResourceLocalizer metinleri süreç içinde 30 saniye tutar; ham SQL ile, başka bir API örneğinde ya da DbMigrator'da değişen bir metin en geç 30 saniyede görünür.
Sozsoft.Settings Ayar tanımları, sağlayıcılar, şifreleme bayrakları ve ayar ekranı metadata'sı.
Sozsoft.Notifications Bildirim tipleri, kuralları ve kullanıcı bildirimleri; kanal bazlı yönlendirme.
Sozsoft.MailQueue Şablon (Scriban) tabanlı gövde üretimi, ek dosya yaşam döngüsü, kuyruk çalıştırma ve günlükleme.
Sozsoft.Sender Amazon SES (mail), Posta Güvercini (SMS), WhatsApp Cloud API, Rocket.Chat göndericileri.
Sozsoft.SqlQueryManager SQL nesne yönetimi, sorgu çalıştırma, tablo/kolon metadata'sı.
Sozsoft.Mcp MCP (Model Context Protocol) sunucusu: /mcp Streamable HTTP ucu, kiracı bazlı araç seti, şema keşfi, sorgu, veri yazma ve (host) tablo/ekran/bileşen geliştirme. Kiracı kullanıcısı veri okur ve ekranın C/U/D sözleşmesinden geçerek satır yazar; geliştirme araçları yalnızca host'tadır. Bkz. §13.9.

10.2. Uygulama servisleri (api/src/Sozsoft.Platform.Application)

AiBots · AuditLogs · BackgroundWorker · Blog · Branch · Dashboard · DataSource · DeveloperKit · FileManagement · Forum · GlobalSearch · Hr · Identity · Intranet · ListForms (+ Administration) · Menu · Messenger · Note · OrgChart · OrganizationUnits · Public · Routes · Tenants · Videoroom

10.3. Yönetim ekranları (UI)

Alan Route Açıklama
Dashboard /admin/dashboard Widget tabanlı özet ekran.
Kullanıcı / Rol /admin/list/..., /admin/users/detail/:userId Kullanıcı, rol ve permission atamaları.
Organizasyon birimleri /admin/ous OU ağacı ve üyelik yönetimi.
Tenant yönetimi ListForm ekranları Tenant kayıtları, bağlantı dizeleri, siparişten otomatik tenant açma.
Ayarlar /admin/settings Sistem ve entegrasyon ayarları.
Menü yöneticisi /admin/menuManager Menü ağacı düzenleme.
Dosya yöneticisi /admin/files Klasör/dosya işlemleri.
Aktivite ve denetim kaydı /admin/activityLog, /admin/changeLog Kullanıcı hareketleri ve ABP audit log detayı.
Bildirimler /admin/profile/notification-settings, bildirim ekranları Kanal tercihleri ve bildirim oluşturma.
Forum /admin/forum Forum kullanımı; yönetim App.ForumManagement altındaki ListForm ekranlarıyla yapılır.
Intranet Intranet dashboard ve widget'ları Duyuru, anket, sosyal duvar, etkinlik; ayrıca İK modüllerinin çalışana ait özetleri (Performans Yönetimi: Performansım, Ekibimin Performansı, 360 Taleplerim — custom component). Hangi widget'ın hangi kolonda, hangi sırada ve hangi yetkiyle yayınlandığı Sas_T_IntranetWidget kaydından gelir; kayıtlar configs/seeds/data/App.DeveloperKit.IntranetWidgets.json ile senkron tutulur.
Video oda /admin/videoroom/* Oda listesi, detay, planlama, katılımcı ve ekran paylaşımı.
Raporlar `/admin/reports/:report/view design/...`
AI asistanı /admin/ai n8n webhook üzerinden sohbet arayüzü.
Profil /admin/profile/* Genel bilgiler, çalışan bilgileri, izinlerim, puantajım, performansım, şifre, bildirim ayarları. Genel ve Çalışan Bilgileri api/account/my-profile ucundan okunur. ABP'nin eşlemesi yalnızca modül uzantısında tanımlı ek alanları taşıdığı için PlatformProfileAppService kullanıcının kendi kaydındaki alanları (ProfileExtraProperties; sağlık, ücret ve işten çıkış alanları hariç) ve departman/pozisyon/şube/rol adlarını cevaba ekler. Çalışan Bilgileri salt okunurdur. İzin, puantaj ve performans sekmeleri çekirdek, salt okunur bileşenlerdir: App.Hr.My* persona ekranlarının satırlarını list-form-select ile okuyup kart + tablo olarak gösterir, düzenle/sil çizmez (tek aksiyon izin talebi oluşturma). Sekme, ekranlarından birinin yetkisi yoksa görünmez.

10.4. Public site

/home · /about · /contact (Custom Component) · /products · /checkout · /payment · /success · /blog · /blog/:id · /demo (fiziksel sayfalar).

Ziyaretçiye açık kısım iki parçadan oluşur ve ikisi de veritabanında yaşar:

Menü. Public menüler Menu tablosunda ModuleId = "Pub" modülü altında tutulur ve Menü Yönetimi ekranından girilir (sözleşme: PlatformConsts.PublicSite). App.Public.Header kökü hem üst navigasyonu hem de alt bilgideki hızlı bağlantıları besler — ikisi de PublicNav düğümüdür, menü bir kez eklenir iki yerde birden görünür; alt kaydı olan bir menü üstte açılır liste olur. Alt bilgide cta kayıtları (Giriş) yer almaz: oradaki düğüm source="navigation" ile çizilir. Url sayfa yolu, Icon react-icons adı, Order sıra, IsDisabled yayın anahtarı, DisplayName dil anahtarıdır; CssClass içinde cta geçen kayıt düz bağlantı yerine vurgulu buton olarak çizilir. Menü ucu [Authorize] olduğu için menüler uygulama konfigürasyonunun publicMenus alanıyla taşınır (PlatformApplicationConfigurationContributor), istemcide usePublicMenu okur. Pub modülü yönetim navigasyonuna girmez (abpConfig.model.ts menüyü kurarken bu modülü eler; Menü Yönetimi ekranı elemez, menüler oradan düzenlenir). Seed: configs/seeds/data/App.Menus.Module.json ve App.Menus.Menu.json.

Sayfa. Her public sayfa bir Custom Component'tir: RoutePath /admin/ ile başlamadığı sürece bileşen otomatik olarak public route olur ve PublicLayout altında yayınlanır. Tasarım yalnızca DeveloperKit > Custom Components (Visual Designer) üzerinden değişir. Aynı yolu bir fiziksel route ile paylaşan custom component kazanır; karşılığı olan dosyası bulunmayan rota kaydı ise hiç yüklenmez (hasPhysicalComponent) — seeder yalnızca eklediği için eski kayıtlar veritabanında kalsa bile ekran patlamaz. Seed: configs/seeds/data/App.DeveloperKit.CustomComponents/{Id}.json; ekran klasör modundadır, her bileşen kendi satır dosyasındadır (dosya adı satırın Id'si).

Runtime kapsamı sınırlıdır: bileşen kodundaki import satırları derlemede temizlenir, kullanılabilenler React, ui kit, PlatformViewHost, apiService, axios, DOMPurify, translate, checkPermission, getCulture ve katalog düğümleri (components/componentEditor/runtimeNodes.ts). react-icons ve react-router yoktur — ikon <PlatformIcon name="FaUsers" size={40} className="..." /> ile ada göre çizilir (menü ikonlarıyla aynı sözlük), gezinme <a href> ile yazılır. Kullanıcıya görünen metinler dil anahtarı üzerinden gelir.

Tailwind sınıf yüzeyi. Bileşenin kodu veritabanında yaşar; derleme sırasında taranan tek kaynak seed dosyalarıdır (configs/seeds/*/data/App.DeveloperKit.CustomComponents/{Id}.json). Bu klasör depo kökünde, ui/ dışındadır — derleme bağlamı yalnızca ui/ klasörü olduğunda (docker, ayrı checkout) glob boş döner ve seed'e özgü sınıflar (md:col-span-4, bg-gray-950/80, opacity-20) CSS'e hiç girmez: uygulama yerelde doğru, sunucuda stilsiz görünür. Bu yüzden aynı sınıflar var olan ui/safelist.txt dosyasına da yazılır. Dosyayı zaten twSafelistGenerator eklentisi üretiyor (renk/ölçü listesi); eklenti artık seed dosyalarındaki className değerlerini de toplayıp # --- custom component classes --- işaretinin altına ekliyor. safelist.txt ui/ içinde ve depoda olduğu için her ortamda taranır. Seed kökü görünmüyorsa işaretin altındaki liste olduğu gibi korunur — eksik bir bağlam listeyi silip sorunu büyütmez. Ayrı bir betik ya da ikinci bir safelist dosyası yoktur; liste her npm run build / npm start ile tazelenir.

Katalog düğümleri. Kanvasın doğrudan çizdiği, derlenen bileşen listesinde yer almayan bileşenler tek bir sözlükte durur (RUNTIME_NODE_COMPONENTS): kanvas onu VisualCanvas içinde, üretilen kod ise runtime kapsamı üzerinden kullanır — iki taraf aynı kaynaktan beslendiği için tasarım ve çalışma zamanı ayrışamaz. Sözlükte PlatformIcon, BarcodeScanField (components/shared/BarcodeScanner), BarcodeViewer (components/shared/BarcodeViewer.tsx) ve public site parçaları (components/publicSite) bulunur. Yeni bir düğüm eklerken: bileşen components/publicSite (ya da uygun klasör) → runtimeNodes.ts sözlüğü → catalog.ts tanımı (kind: 'custom') → data/App.Languages.LanguageText.json açıklama anahtarı.

Barkod. Okuma ve çizme ayrı yollardır, ikisi de utils/barcode.ts içindedir ve zxing-wasm yalnızca gerektiğinde lazy iner. Okuma: Chromium'da yerleşik BarcodeDetector, olmayan tarayıcıda zxing-wasm/reader (BarcodeScanField, BarcodeScannerDialog). Çizme: zxing-wasm/writer (writeBarcodeSvg → BarcodeViewer); SVG üretilir, böylece büyütmede ve baskıda kırılmaz. Desteklenen çizim formatları BARCODE_WRITE_FORMATS listesindedir (QRCode, Code39, Code93, Code128, EAN13/8, UPCA/E, ITF, Codabar, DataMatrix, PDF417, Aztec) ve katalogdaki format seçenekleri de bu listeden üretilir — liste tek yerde değişir. BarcodeViewer bir value propu taşıdığı için tasarımcıdaki Form düğümünün içinde tıpkı bir Input gibi bir kolona bağlanabilir; kayıt değiştikçe kod yeniden çizilir.

Elle yazılmış bir bileşen Visual Designer'da kod modunda açılır: tasarımcı yalnızca kendi ürettiği kodu düğüm ağacına çevirir (isDesignerGeneratedCode), aksi hâlde tanımadığı JSX'i düşürüp kaydederken kodu yok ederdi.

Public bileşenlerin tamamı — /home, /about, /contact sayfaları ile üst ve alt bilgi (PublicHeader, PublicFooter; rotaları yok) — tasarımcı belgesi olarak tutulur: Props alanındaki visualDesigner düğüm ağacı kaynaktır (sourceMode: "visual"), Code ondan üretilir (generateDesignerCode) ve runtime da bileşeni bu belgeden derler. Yani hepsi kanvasta görünür, sürükle-bırak ve özellik paneliyle düzenlenir. Belgeler div/h1-h5/p/span/img/a HTML düğümleri ve PlatformIcon ikon düğümüyle kurulmuştur; :: ile başlayan metinler üretilen kodda translate() çağrısına döner. RoutePath boş bırakılabilir; rotası olmayan bir bileşen yalnızca başka bir ekranın içinde çizilir (alt bilgi gibi).

Üst ve alt bilgi de birer Custom Component'tir (PublicHeader, PublicFooter; rota yok): PublicLayout ikisini de renderComponent ile çizer ve geriye yalnızca rota değişiminde başa sarma ile demo penceresi kalır. Bileşen kayıtlı değilse o parça çizilmez.

Üst bilginin davranışı (menü listesi, aktif bağlantı, kaydırmada değişen görünüm, mobil panel, tema/dil/demo kontrolleri) components/publicSite altındaki katalog düğümlerinde yaşar; yerleşim ve sınıflar tasarımcıdadır: PublicHeaderBar (kaydırıldığında scrolledClassName'e geçen kabuk), PublicNav (source: navigation/actions/all, variant: horizontal/vertical), PublicMobileMenu (hamburger + içeriği tasarlanan panel), PublicLogo, PublicThemeToggle, PublicLanguageSelector, PublicDemoButton.

Rotası olmayan bileşende RoutePath NULL yazılır; Sas_H_CustomComponent'in RoutePath tekil indeksi yalnızca dolu değerleri kapsar ([IsDeleted] = 0 AND [RoutePath] IS NOT NULL), aksi hâlde ikinci rotasız bileşen birincisiyle çakışırdı.

/home, /about, /contact bu yolla çalışır (PublicHomePage, PublicAboutPage, PublicContactPage); üst ve alt bilgi rotasızdır (PublicHeader, PublicFooter).

Yalnızca ikon taşıyan bir bağlantı ya da düğmenin ekran okuyucuda adı olmaz; a ve button düğümleri bunun için aria-label özelliği taşır (componentDefinitions.ts). Görsel düğümüne width/height yazmak da tarayıcının yeri önceden ayırmasını sağlar ve yerleşim kaymasını (CLS) önler — gösterim ölçüsü className'den gelmeye devam eder.

Veriden beslenen public sayfalar (/products, /checkout, /payment, /success, /blog, /blog/:id, /demo) fiziksel kalır; içerik sayfaları Custom Component'tir. Blog listesi ve detayı PublicAppService'in post-list / post-by-slug uçlarından beslenir, yönetimi App.BlogManagement.* ekranlarındadır.

SEO. Ziyaretçiye açık yüzeyin arama motoru sözleşmesi üç parçadır:

Parça Nerede Ne taşır
Genel varsayılanlar App.SiteManagement.Seo.* ayarları (Ayarlar ekranı, yetki App.SiteManagement.Seo) Site adresi, site adı, varsayılan başlık/açıklama/paylaşım görseli, Twitter hesabı, diller, robots yönergesi, Google doğrulaması, sosyal hesaplar, varsayılan anahtar kelimeler (DefaultKeywords), sitemap anahtarı (SitemapEnabled), sitemap dışı yollar (SitemapExcludePaths)
Yol bazlı alanlar Yolu tanımlayan kaydın Seo* kolonları: Route (Sas_H_Route, ekran Menü Yönetimi › Route) ve CustomComponent (Sas_H_CustomComponent, ekran DeveloperKit › Custom Components) Yolun başlığı, açıklaması, anahtar kelimeleri, paylaşım görseli, kanonik adresi, SeoNoIndex, sitemap davranışı ve ek JSON-LD
İstemci components/shared/SeoHead.tsx title, description, robots, canonical, og:/twitter: kartı, og:locale:alternate ve JSON-LD

Ayrı bir SEO tablosu yoktur: meta, yolu tanımlayan kaydın üzerinde yaşar. SeoAppService iki kaynağı tek sözleşmede birleştirir — RouteType = public olan Route satırları ve rota taşıyan aktif Custom Component'ler. Aynı yolu ikisi de tanımlıyorsa bileşen kazanır; çalışma zamanında da bileşen fiziksel rotayı gölgeler.

Alanlar ve ayarlar uygulama konfigürasyonunun seo alanıyla ziyaretçiye taşınır (PlatformApplicationConfigurationContributor → SeoAppService.GetConfigAsync); sayfa ayrı bir istek atmaz, meta ilk boyamada hazırdır. Yol çözümü daraldıkça öncelik kazanır: birebir yol, sonra en uzun joker (/blog/*). Metin alanları kültür başına çoğalmaz: ::Anahtar biçimindeki değer istemcide çevrilir (resolveSeoText), düz metin olduğu gibi kullanılır. Sayfanın kendi SeoHead'i, kabuğun (PublicLayout) fallback kaydını her zaman yener — PageTitle ile aynı çözümleme.

sitemap.xml ve robots.txt aynı servisten üretilir (SeoController, anonim). Sitemap'in kaynakları: Pub menüleri, aktif custom component rotaları, yayınlanmış blog yazıları ve yolu tanımlayan kayıtların Seo* alanları; /admin yolları, parametreli rotalar (/blog/:id) ve SeoNoIndex işaretli ya da SeoIncludeInSitemap = false olan yollar elenir. App.SiteManagement.Seo.SitemapExcludePaths (satır başına bir yol, sonu * ise önek) hem sitemap'ten düşer hem robots.txt'e Disallow olarak yazılır; robots.txt ayrıca /admin/, /auth/, /checkout, /payment, /success yollarını her zaman kapatır. SitemapEnabled kapalıyken sitemap.xml 404 döner ve robots'a Sitemap: satırı yazılmaz. Arama motorları bu iki dosyayı sitenin kök dizininde aradığı için UI sunucusu her iki yolu da API'ye geçirir (vite.config.ts > seoProxy, hem server hem preview); ayrı bir ters vekil kuralı gerekmez. VITE_API_URL tanımlı değilse proxy kurulmaz ve ui/public/robots.txt içindeki statik varsayılan geçerli kalır.

Yönetim ve oturum yüzeyi noindex gelir (Layouts.tsx), satın alma akışı (/checkout, /payment, /success) kendi sayfasında noIndex bildirir.

Ziyaretçiye açık yeni bir yol üreten her iş SEO alanlarını da doldurur; iki ekranın da SeedSync* bayrakları açık olduğu için ekrandan girilen değer data/App.Menus.Routes.json ya da data/App.DeveloperKit.CustomComponents/{Id}.json dosyasına düşer. Ayrıntı: .github/instructions/lowcode.instructions.md §0.9.

Eski statik içerik sayfaları ve sayfa içi tasarım modu (?design=1) kaldırılmıştır: views/public/Home|About|Contact|Services, views/public/designer/, bunlara özel istemci servisleri; sunucuda Home/About/Contact/Service entity'leri, PublicAppService sayfa Get/Save uçları ve DTO'ları, TenantData.json + TenantDataSeeder, App.{Home,About,Services,Contact} yetkileri ve .Design alt yetkileri, /admin/public/*/designer rota ve menüleri.

10.5. Modül dokümanları (configs/seeds/docs/modules/)

Konfigürasyonla üretilen modüllerin çalışma şekli, keşif analizi ve kararları kök menü başına tek dosyadadır; menünün altındaki her dal dosyada bir bölümdür (module-doc:section). "Bu ekran nasıl çalışıyor?" sorusunun cevabı oradadır (MCP'de get_module_doc; ekran ya da dal kodu verilirse yalnızca o bölüm döner). Dosya adı kök menünün kodudur (App.Hr → App.Hr.md, App.Prc → App.Prc.md); sabit bir liste yoktur, menü ağacından okunur. Wizard deploy'u (ekran ya da MCP) yeni kök menünün dosyasını, yeni dalın bölümünü menü sırasındaki yerine açar ve sondaki "Ek — Ekran envanteri"ni her deploy, silme, tasarımcı kaydı ve import'ta wizard dosyalarından yeniden yazar; anlatım bölümlerini üreten doldurur (MCP'de save_module_doc). Wizard export'u dokümanı taşır (lowcode.instructions.md §0.13). Liste ekranlarının başlığındaki ⓘ simgesi ise ekranın kendi ayrıntılı açıklamasını gösterir (DescTr/DescEn, lowcode.instructions.md §4.4.2).

Doküman Kök menü Bölümler
App.Administration.md Yönetim (App.Administration) Tanımlamalar (Kontaklar: satış ve satınalmanın ortak müşteri/tedarikçi kartı ve sekmeleri, kontak etiketleri)
App.Hr.md İnsan Kaynakları (App.Hr) Tanımlamalar (dizin + genel tanımlar), Organizasyon ve Yetkinlik, Personel Kaydı (Kimlik › Kullanıcılar › detay), İşe Alım, Çalışma Takvimi ve Vardiyalar, İzin Yönetimi, Puantaj ve Devam Takibi, Sağlık Kartları, Eğitimler, Performans Yönetimi, Disiplin Dosyaları, Araçlar, Ziyaretçiler, Olay Günlüğü, Intranet
App.Prc.md Satınalma (App.Prc) Tanımlamalar (ödeme/teslimat koşulları, ödeme/teslim yöntemleri)
App.Inventory.md Envanter (App.Inventory) Tanımlamalar (ürün kategorileri, nitelikler, ürünler ve varyantlar, reçeteler, reçete patlatma)
App.Production.md Üretim (App.Production) Tanımlamalar (iş merkezi etiketleri, iş merkezleri, operasyonlar)

11. Kimlik, Oturum ve Güvenlik

11.1. Kimlik doğrulama

  • OpenIddict password + refresh token akışı. Token uç noktası: POST /connect/token (grant_type=password, client_id=Platform_PublicApi, scope=offline_access Platform).
  • Token ömürleri OpenIddict:TokenLifetimes ile yapılandırılır (varsayılan 60 dk access, 90 dk refresh).
  • Swagger için ayrı istemci: AuthServer:SwaggerClientId.
  • İki aşamalı doğrulama (2FA) ve hesap doğrulama (e-posta/telefon) desteklenir.
  • Kayıt ve iletişim formlarında Cloudflare Turnstile tabanlı captcha (CaptchaMiddleware).

11.2. Oturum yönetimi

  • PlatformSessionValidationMiddleware her istekte oturumun hâlâ geçerli olduğunu doğrular.
  • PlatformSessionRevocationHandler token iptalinde oturumu sonlandırır.
  • PlatformSessionCleanupWorker süresi dolmuş oturumları temizler.
  • Eş zamanlı kullanıcı limiti aşıldığında giriş reddedilir.

11.3. Giriş engelleri

UserCannotSignInErrors altında tanımlı, kullanıcıya lokalize mesajla dönen durumlar:

Kod Anlamı
LoginNotAllowed_NotVerified Hesap doğrulanmamış.
LoginNotAllowed_NotAllowedIp IP kısıtlaması (IpRestriction).
LoginNotAllowed_WorkHour Çalışma saati kısıtlaması (WorkHour).
LoginNotAllowed_LoginEndDateDue Kullanıcının giriş yetki süresi dolmuş.
ShouldChangePasswordOnNextLogin / ShouldChangePasswordPeriodic Zorunlu şifre değişimi.
LoginNotAllowed_TenantIsPassive / TenantNotFound Tenant pasif veya bulunamadı.
LoginNotAllowed_BranchLimit Şube lisans limiti aşıldı.
LoginNotAllowed_ConcurrentUserLimit Eş zamanlı kullanıcı limiti aşıldı.

11.4. Güvenlik kuralları

  • Tüm katmanlarda RBAC ve yetki tabanlı görünürlük zorunludur.
  • Dinamik SQL'de yalnızca parametreli desenler kullanılır; kullanıcı girdisi string birleştirme ile sorguya eklenmez.
  • Her sorgu ve eylemde tenant izolasyonu korunur (IsTenant, IsBranch, IsOrganizationUnit bayrakları ve ABP tenant filtreleri).
  • Gerçek kimlik bilgileri, token ve anahtarlar örneklerde/dokümanda yer almaz; yer tutucu kullanılır.
  • Denetim: kritik işlemler ABP Audit Log'a yazılır; yüksek frekanslı list-form-customization çağrıları denetim dışı bırakılmıştır.

12. Çok Kiracılılık (Multi-Tenancy)

  • PlatformConsts.IsMultiTenant = true.
  • Tenant çözümleme host adı (subdomain) ve ABP tenant resolver'ları ile yapılır (utils/subdomain.ts).
  • Tenant başına ayrı veritabanı: TenantConnectionString kayıtları ile her tenant kendi veritabanına yönlendirilebilir. Bağlantı kaydedildiğinde kiracı adıyla bir DataSource açılır (TenantConnectionStringEventHandler). Şema ve seed DbMigrator ile kurulur: tam migrate bütün kiracıları dolaşır, Tenants ekranının Seed sekmesi yalnızca seçilen kiracıyı kurar (POST /api/setup/tenants/{id}/seed, TenantSeedOptions). EF ile yalnızca IMultiTenant varlıklar kiracı veritabanına gider. ListForm, menü ve yetki tanımı gibi kiracısız varlıkları ABP her zaman host'ta okur. Kiracı tablolarının satırları DataSourceManager üzerinden kiracının veri kaynağına gider.
  • Şube (Branch): Tenant altındaki fiziksel/örgütsel kırılım; kullanıcı–şube eşlemesi BranchUsers ile tutulur, lisans limiti girişte kontrol edilir.
  • Organizasyon birimi: ABP OrganizationUnit ağacı; ListForm'lar OU bazlı filtrelenebilir.
  • Lokalizasyon: isteğin kültürü (metin, tarih, sayı) UseAbpRequestLocalization ile UI'ın gönderdiği Accept-Language'tan kurulur. TenantLocalizationMiddleware + TenantLocalizationInitializer istek dışı iş parçacıklarının (Hangfire, worker) varsayılan kültürünü ve IClock tipini global Abp.Localization.DefaultLanguage / Abp.Timing.TimeZone ayarlarından tutar; değerler dakikada en fazla bir kez okunur ve yalnızca değişince yazılır.

13. Bildirim ve Entegrasyonlar

13.1. Kanallar

Sms · Mail · Rocket · Desktop · UiActivity · UiToast · WhatsApp · Telegram

Kanal kayıt defteri iki yerdedir: backend NotificationChannels (Domain.Shared), UI constants/notification-channel.enum.ts (kanal değeri + NotificationChannelColors + NotificationChannelIcons). Kanal listesi gösteren ekranlar (tercih matrisi, Activity Log filtresi) listeyi bu kayıt defterinden üretir; etiket her yerde App.NotificationChannel.{Kanal} dil anahtarından okunur, kanal lookup'ı (ListFormSeeder_DefaultJsons) da aynı anahtarları kullanır. Yeni kanal = iki kayıt defteri + renk + ikon + dil anahtarı + NotificationWorker gönderimi.

13.2. Yönlendirme modeli

Bildirim tip + kanal eşleşmesiyle yönlendirilir. Alıcı hedefleme seçenekleri: All, User, Role, OrganizationUnit, Custom. Kullanıcılar kendi bildirim tercihlerini /admin/profile/notification-settings üzerinden kanal bazında yönetir.

Bildirim panelinde, masaüstü bildiriminde ve Activity Log'da başlık kaydın Subject'idir (NotificationDto.Subject; bildirimi yazan prosedür dil anahtarından çevrilmiş metni yazar). Tip adı (PerformansDurumDegisti gibi) koddur; yalnız Subject boşsa gösterilir.

13.3. Kayıt durumu: gönderildi → okundu

Bir bildirim kaydı iki bayrak taşır ve sıra değişmez: önce IsSent, sonra IsRead. Gönderilmemiş bir kayıt okunmuş olamaz.

Kanal IsSent ne zaman
UiActivity Kayıt oluşurken (NotificationManager); ayrı bir gönderim adımı yoktur, kayıt doğrudan kullanıcının akışına yazılır
UiToast · Desktop Tarayıcıda gösterildiğinde, UI updateSentMany ile işaretler
Mail · Sms · Rocket · WhatsApp · Telegram NotificationWorker sağlayıcıya teslim ettiğinde

IsRead ve ReadTime birlikte yazılır (NotificationAppService.UpdateRead*): okundu işaretinde ReadTime = Clock.Now, okunmadıya çevirmede null. IsSent = false olan kayıt okundu işaretlenmez — tekil ve toplu okuma yolları bu kaydı sessizce atlar, gönderim gerçekleştiğinde okunabilir olur. Bu yüzden UI işaretlemeden sonra listeyi sunucudan tazeler; iyimser güncelleme rozette yanlış sayı bırakırdı.

Tarayıcı kanalları tek kümededir (NotificationChannels.BrowserChannels); sent-many ucu yalnızca bu kanalları işaretler, böylece istemci Mail/Sms gibi bir kanalı "gönderildi" yapamaz. Masaüstü bildirimi izin verilmemişse hiç işaretlenmez — işaretlenseydi izin sonradan verildiğinde de gösterilmezdi.

13.4. Gerçek zamanlı iletim

Bildirim üretildiği anda açık istemcilere SignalR ile duyurulur: hub /notificationhub, bağlantı kullanıcı başına gruplanır (NotificationHub.UserGroupName, kiracı + kullanıcı). NotificationManager kayıtları yazdıktan sonra duyurur (INotificationRealtimeNotifier); duyuru yükü yalnızca hangi kanallarda kayıt oluştuğunu söyler, istemci listesini yine sunucudan okur — okundu/gönderildi bayrakları tek kaynaktan gelir.

Katman sınırı: sözleşme bildirim modülünün Domain katmanındadır (INotificationRealtimeNotifier), SignalR gerçeklemesi ise barındıran uygulamadadır (Sozsoft.Platform.Application/Notifications/, MessengerHub ile aynı yer) — modül katmanları ASP.NET Core'a bağlanmaz. Gerçekleme [Dependency(ReplaceServices = true)] ile devreye girer; SignalR olmayan barındırmalarda (DbMigrator, testler) NullNotificationRealtimeNotifier kalır.

Duyuru en iyi çabadır: bağlantı yoksa ya da mesaj kaybolursa bildirim kaybolmaz — istemcideki yedek yoklama (5 dakika, sekme öne geldiğinde ayrıca) aynı kaydı getirir. Ters vekilde /notificationhub kendi bloğunda WebSocket yükseltmesiyle karşılanır (configs/deployment/configs/nginx.conf); token sorgu dizesinden geldiği için host bu yolu da Authorization başlığına çevirir.

Masaüstü bildirimine tıklamak uygulamaya döner: service worker açık sekmeyi öne alıp bildirimin data.url adresine götürür, sekme yoksa yenisini açar (varsayılan hedef Activity Log).

13.5. Kullanıcı tercihleri

Kurallar alıcı kümesini belirler; kullanıcının profilinden yazdığı tercih (IsCustomized, RecipientType = User) kural değildir, yalnızca sahibinin o tür/kanal aboneliğini kapatır (NotificationManager). Tercihi kural gibi işletmek, bir kullanıcının kendi tercihini yazmasıyla aynı tür ve kanaldaki herkesin bildirimini kesmesi anlamına gelirdi. IsFixed kural zorunludur, tercih dikkate alınmaz. Aynı kullanıcıyı birden çok kural hedeflerse (All + Role) kanal başına tek kayıt üretilir.

Tercih ekranı (/admin/profile/notification-settings) satırlarını bildirim tipinin id'si üzerinden eşler; tip adı yalnızca gösterimdir. NotificationType.Description alanı aynı ekranda ipucu olarak görünür ve Bildirim Türleri ekranından yazılır. Ekran geniş ekranda tür × kanal matrisi, dar ekranda kart listesidir; hücre Switcher'dır, IsFixed kural kilit ikonuyla gösterilir ve değiştirilemez, kullanıcının değiştirdiği tür işaretlenir. Kayıt sırasında yalnızca ilgili hücre/satır kilitlenir. Satırı varsayılana döndürmek DELETE /api/app/notification-rule/my-notification-rules/{notificationTypeId} ile kullanıcının o türdeki tercihlerini siler.

13.5.1. Bildirim ekranı ve elle gönderim (Administration › Bildirimler)

Bildirimin tamamı Administration menüsünde App.Administration.Notifications grubundadır: Bildirim Türleri, Bildirim Kuralları, Bildirimler, Bildirim Şablonları. Grup Agenda'nın üstündedir; Saas altında bildirim ekranı yoktur.

Tablolar kiracı kapsamındadır (Adm_T_NotificationTemplate, Adm_T_NotificationType, Adm_T_NotificationRule, Adm_T_Notification; dördü de IMultiTenant). Her kiracı kendi türünü, kuralını ve kuyruğunu görür; host kaydı (TenantId = null) yalnızca host oturumunda görünür. Yeni bir kiracı oluşturulduğunda varsayılan tür ve kural kümesi o kiracıya kopyalanır. Kendi veritabanı olan kiracı kümeyi seed dosyasından alır (ListFormSeedDataApplier). Host veritabanını paylaşan kiracıya ise NotificationTenantDefaultsSeedContributor kopyalar: kaynak data/App.Notifications.Notification{Types,Rules}.json dosyalarıdır, satırlar yeni kimliklerle yazılır ve eşleşme ad üzerinden yapıldığı için ikinci çalıştırma bir şey eklemez; şablonlar kopyalanmaz. İki yol aynı kiracıda çalışmaz, yoksa türler iki kez yazılırdı.

Bildirimler ekranı (App.Notifications.Notification) hem kuyruk günlüğü hem gönderim ekranıdır: liste kural motorunun ürettiklerini de gösterir, ekleme formu ise elle gönderimdir. Ayrı bir manuel gönderim kaydı yoktur — gönderimin kaydı bildirimin kendisidir. Ekleme anında yazma işi list-form-dynamic-api/notification-insert ucuna gider (InsertServiceAddress); uç seçilen alıcı × kanal açılımını INotificationManager.SendManualNotificationAsync ile kuyruğa yazar. Bildirim Kuralları ekranındaki CreateNotification diyaloğu ise aynı yöneticinin SendNotificationAsync yolunu kullanır: türün kurallarını işletir ve kullanıcı tercihini dikkate alır; bu uç alıcıyı ekrandan alır ve tercihi okumaz. Güncelleme kapalıdır, silme kaydı kaldırır.

Formdaki NotificationChannel ve UserId alanları listede tek değerlidir (her satır bir alıcı × kanal), formda dxTagBox ile çoklu seçilir; tipli DTO yolunda çoklu değeri MultiValueStringJsonConverter taşır (CreateNotificationInput).

Kural Nerede
Bildirim tipi her zaman Manuel NotificationConsts.ManualTypeName; tip kaydı App.Notifications.NotificationTypes.json
Notification.NotificationRuleId NOT NULL olduğu için tipin çapa kuralına bağlanır (RecipientType = Custom, IsActive = false) App.Notifications.NotificationRules.json; kural pasif olduğu için kural motoru ve profil tercih ekranında görünmez
Kullanıcı tercihi okunmaz (zorunlu bildirim gibi davranır) NotificationManager.SendManualNotificationAsync
Alıcı listesi kiracı filtresinden geçer Kullanıcı sorgusu ABP kiracı filtresiyle çalışır; istekteki kimlikler değiştirilse bile başka kiracıya gönderilemez
İlgili kanalda adresi olmayan alıcı atlanır Hiç satır üretilemezse uç App.Notifications.SkippedChannel gerekçesiyle hata döndürür; kısmi atlamada gönderim sürer
Metnin geldiği şablon kayda yazılır Notification.NotificationTemplateId (provenans; tür değil, metin kaynağı)
Kanaldan çıkış yine NotificationWorker UiActivity oluşurken iletilmiş sayılır, tarayıcı kanallarını istemci işaretler (§13.3)

Şablonlar (Administration › Bildirimler › Bildirim Şablonları, App.Notifications.NotificationTemplates, Adm_T_NotificationTemplate) mesaj metnini taşır: Code, Name, Channel (boş = tüm kanallar), Subject, Body, IsActive. Örnek şablonlar seed ile gelir (data/App.Notifications.NotificationTemplates.json; ekran SeedFilePath ile bu dosyaya aynalanır). Gönderimde şablon seçilirse ekranda boş bırakılan konu ve gövde şablondan tamamlanır; ekranda yazılan metin şablonu ezer.

Gövdedeki yer tutucular alıcı bazında çözülür (NotificationMessageTokens): {AdSoyad}, {KullaniciAdi}, {Eposta}, {Tarih}, {Saat}, {Kiraci}. Bildirim kaydının Message kolonu 2048 karakterdir; yer tutucular çözülünce metin bu sınırda kırpılır.

Mail konu satırı: bildirim kaydı Subject kolonu taşır (Adm_T_Notification, 256). NotificationWorker konuyu bu kolondan alır; kolon boşsa bildirim tipinin adına düşer (Platform Bildirim: {tip}). Kural motorundan gelen bildirimlerde kolon boş kalır, elle gönderimde ekrandan ya da şablondan gelir — yani konu artık gövdeye gömülmez.

13.6. Göndericiler (Sozsoft.Sender)

Kanal Sağlayıcı
E-posta ABP Emailing + Amazon SES (AmazonSesEmailSender), arka plan iş kuyruğu ile
SMS Posta Güvercini HTTP API
WhatsApp WhatsApp Cloud API (şablon tabanlı)
Rocket.Chat Rocket.Chat HTTP API
Telegram Telegram Bot API (SozsoftTelegramSender; alıcı kullanıcının TelegramChatId özelliği)

Tüm sağlayıcı kimlik bilgileri ayar ekranından yönetilir (App.Settings.* anahtarları); koda gömülmez.

13.7. Mail kuyruğu

Sozsoft.MailQueue; şablondan gövde üretir, PDF/XLS/TXT çıktıları ve ek dosyaları yönetir, kuyruğu arka plan işçisiyle çalıştırır ve sonuçları günlükler.

13.8. AI entegrasyonu

AiBot kayıtları tenant bazlıdır; n8n webhook üzerinden bir niyet sınıflandırma adımı ve ardından sohbet ya da SQL dalı çalışır (configs/ai/Chat.json). Model, DevOps makinesindeki Ollama'da yerel olarak koşar (http://ollama:11434/api/chat, varsayılan qwen2.5:3b) — dışarıya model çağrısı yapılmaz. UI tarafında /admin/ai ve header'daki asistan bileşeni bu uç noktayı kullanır.

Ollama'ya yüklü modelleri yönetmek için configs/ai/Kurulum.txt, sohbet arayüzü (Open WebUI) ve alan adı yönlendirmesi için §20 kullanılır. Akıştaki model adı değiştirilirse configs/ai/Chat.json içindeki üç varsayılanın (intentModel, chatModel, sqlModel) birlikte güncellenmesi gerekir; biri kalırsa o dal "model not found" ile düşer.

13.9. MCP sunucusu

Yapay zekâ istemcileri (Claude Code, Claude Desktop, ChatGPT) platforma /mcp ucundan bağlanır. Uç, API imajının içinde aynı portta (8080) yayınlanır; ayrı bir container yoktur. Modül api/modules/Sozsoft.Mcp altında standart ABP katmanlarıyla durur.

Kimlik ve kiracı. Kimlik doğrulama mevcut OpenIddict bearer token'ıdır. Uç, UseAuthentication → UseAbpOpenIddictValidation → UseMultiTenancy → UseUnitOfWork ardışık düzeninden sonra bağlanır ve açıkça bearer şemasına bağlıdır (token'sız istek giriş sayfasına 302 ile değil, 401 ile döner); kiracı token talebinden ya da __tenant başlığından çözülür. Taşıyıcı durumsuz kiptedir (HttpServerSessionMode.Stateless): her istek kendi ABP kapsamında koşar, ters vekil arkasında oturum yapışkanlığı gerekmez. /mcp istek düzeyindeki unit of work'ün dışındadır (AbpAspNetCoreUnitOfWorkOptions.IgnoredUrls): cevap SSE ile aktığı için istek UoW'si sonuç istemciye gittikten sonra commit edilir ve bağlantı kapanınca geri alınırdı — araç başarılı görünür, EF kayıtları (ListForm, menü, yetki) kaybolur, DDL ve seed dosyası kalırdı. Her MCP servis çağrısı kendi UoW'sini sonuç dönmeden tamamlar.

Kiracı bazlı sözleşme. Sas_H_McpServer tablosundaki kayıt (ekran: App.DeveloperKit.McpServers) hangi araçların yayınlandığını ve sorguların hangi satır tavanıyla koşacağını belirler; sorgular her zaman isteğin kiracısının veritabanında koşar. Tenants boşsa kayıt tüm kiracılara açıktır (AiBot ile aynı sözleşme). Araç listesi bu kayda göre süzülür (AddListToolsFilter) ve çağrı anında aynı kural yeniden uygulanır — süzme tek başına bir yetki sınırı değildir.

Kiracı veri okur ve yazar, geliştirme yapamaz. Kiracı bağlamındaki istekte açık araçlar McpToolNames.TenantTools kümesiyle sınırlıdır: server_info, list_lists, describe_list, search_schema, query_list, run_select, insert_row, update_row, delete_row. AllowedTools bu kümeyi yalnızca daraltabilir. Satır araçları ekranın C/U/D sözleşmesinden ve kiracının kendi veritabanından geçer. create_table, wizard, component ve geliştirme talebi araçları kiracının yetkisi ne olursa olsun listelenmez, çağrılırsa App.Mcp.Error.TenantToolDisabled ile reddedilir. list_queries/run_query da dışarıdadır: procedure ve Custom Endpoint çağrısı ekran yetkisinden geçmeden veri yazabilir. Host kullanıcısı için tek sunucu anahtarı AllowedTools'tur (boşsa tümü); sınırı yetkiler çizer. Karar McpToolPolicy.IsAllowed'dadır, liste süzgeci ve çağrı kapısı aynı kuralı kullanır.

Araçlar.

Araç İş
server_info Bağlantının bağlamı (scope: host / tenant; kiracı adı server.tenantName), açık araçlar, veri sözlüğünün tazelik bilgisi. Sözlüğün dictionaryKey alanı (host'ta Sozsoft) sözlüğün yazıldığı anahtardır, kiracı bağlamını göstermez. Her zaman açıktır.
list_lists Listeler: kod, başlık, açıklama, arkasındaki tablo. Host'ta App.Listforms.Wizard yetkisi olan geliştiriciye veri sözlüğünde henüz olmayan kaynaklar da döner: migrate bekleyen wizard ekranları (sourceKind: pending, alanlar wizard dosyasının Fields bölümünden) ve yalnızca seed scripti olan tablolar (pendingTable, kolonlar scriptten). describe_list ve search_schema aynı kümeyi kullanır; model lookup/widget/view SQL'ini bu adlarla yazar.
describe_list Bir listenin sütunları: alan adı, görünen başlık, SQL tipi, lookup ilişkisi, isSensitive.
search_schema Liste ve sütun adları/başlıkları üzerinde aksan ve büyük/küçük harf duyarsız arama.
query_list Listenin verisini ekranın kendi sorgusuyla (GetSelectAsync) okur: filtre, sıralama, gruplama, özet (DevExtreme biçimi). Sıralama/gruplama/özet JSON'u, arayüzün Utils.ts'te yaptığı gibi select ucunun metin biçimine (`Alan desc
run_select Parametreli tek SELECT; çoklu ifade, DDL ve DML reddedilir, satır tavanı sorguya yazılır. Yalnızca SQL Query Manager yetkisiyle. Sağlayıcı hatası mesajıyla döner (SelectFailed; bilinmeyen kolonda tabloların gerçek kolonlarıyla SelectFailedColumns; veritabanında olmayan ama seed scripti olan tabloda — create_table yazdı, DB Migrate kurmadı — scriptteki kolonlarla SelectTableNotInstalled); araç açıklaması kolonu varsaymamayı, gerekirse önce INFORMATION_SCHEMA.COLUMNS'ı okumayı söyler.
list_queries View/procedure/function nesneleri (SQL Query Manager yetkisiyle) + çağrılabilir Custom Endpoint'ler (App.DeveloperKit.CustomEndpoints.Get ile).
run_query Kayıtlı bir nesneyi ya da Custom Endpoint'i parametreleriyle çağırır; yetkiler list_queries ile aynı.
insert_row / update_row / delete_row ListForm ekranının insert/update/delete sözleşmesinden geçer.
get_dev_guide Üretim sözleşmesi: lowcode / reference belgelerinin başlık listesi ya da istenen bölümü (numarayla, örn. 4.2, ya da başlık metniyle). Bölüm önce istenen, sonra diğer belgede aranır; başlıkla eşleşmeyen terimde (örn. dxImageUpload) terimin geçtiği bölümler (en çok 15, DevGuideSearchMatches), hiç geçmiyorsa başlık listesi döner — hata atılmaz. Çok büyük bir üst bölüm yalnızca giriş metni + alt başlıklarıyla döner.
create_table Yeni tablonun scriptini SQL Query Manager tablo tasarımcısıyla aynı biçimde üretir (McpTableCreator): Id + TenantId + tam audit kolonları şablondan gelir, istemci yalnızca iş kolonlarını verir; tip ve varsayılan değer beyaz listeden geçer, istemci SQL yazamaz. indexes ve foreignKeys ile index/unique/FK aynı scripte varlık kontrollü (tekrar çalışmaya dayanıklı) yazılır; FK'nin hedefi migrate'te önce kurulmalıdır — adı bu tablodan önce sıralanan bir seed tablosu ya da veritabanında kurulu bir platform tablosu (ForeignKeyTableNotInstalledFirst). Script sql/object/{Tablo}.sql dosyasına, PostgreSQL eşi (PostgreSqlTableScript) postgres/object/{Tablo}.sql dosyasına yazılır. Veritabanında çalıştırılmaz: tabloyu kullanıcının çalıştırdığı DB Migrate kurar; o zamana kadar deploy_wizard kolonları scriptten okur (PostgreSqlTableScript.ReadSeedColumns). Yalnızca oluşturur — tablo ya da scripti varsa reddeder. .Write + App.SqlQueryManager ister.
save_sql_object View/procedure/function scriptini sql/object/{Ad}.sql ve postgres/object/{Ad}.sql dosyalarına yazar (McpSqlObjectWriter); veritabanında çalıştırılmaz, nesneyi DB Migrate kurar. Gövde diyalektler arasında güvenle çevrilemediği için iki script de istemciden gelir ve aynı nesneyi tanımlamalıdır (SqlObjectInvalid); ad scriptten okunur, başlık CREATE OR ALTER / CREATE OR REPLACE'e çevrilir. Tablo scriptinin üstüne yazmaz. .Write + App.SqlQueryManager ister.
generate_crud_endpoints SQL Query Manager CRUD diyaloğuyla aynı endpoint kümesini (CrudEndpointSeedFileDto.Generate — diyaloğun servisi de bunu kullanır) yalnızca crud/{Entity}.json dosyasına yazar; kayıtları DB Migrate ekler (CrudEndpointSeedApplier). .Write + App.SqlQueryManager.CrudEndpoints ister.
list_wizards / get_wizard Wizard File Manager dosyaları; get_wizard seed dosyasını diskteki biçimiyle döndürür.
deploy_wizard Wizard'ı Wizard ekranıyla aynı yoldan (IListFormWizardAppService.Create) deploy eder. Girdi seed dosyası biçimi ({"Wizard": …, "ListForm": …, "Fields": […]}) ya da düz cevaplardır; ikisi de WizardSeedFileDto.FromDeployJson'dan geçer: Wizard bölümünde yazılan cevap korunur, yazılmayan ekran alanı ListForm/Fields'tan tamamlanır, iki bölüm de ekrana katman olarak uygulanır (WizardScreenOverlay, üç yönlü: bölümdeki değer wizard'ın kendi ürettiğiyle aynıysa cevap kazanır, farklıysa editör ayarıdır ve korunur; kimlik alanları wizard'dan gelir, tanınmayan/hesaplanan anahtar reddedilir). Böylece MCP, ListForm editörünün yapabildiği her ayarı wizard dosyasına yazar ve replaceFileName ile güncelleme editör ayarlarını silmez. Yeni kaydın form varsayılanı kolonun tablodaki sabit DEFAULT'udur (katalog ya da seed scripti, SqlSeedColumn.ConstantValue; IsActive → true, UomId → Adet, para birimi → TRY tablonun DEFAULT'larından gelir); alana özel değer öğenin DefaultValue'su ile verilir (lowcode.instructions.md §4.3.5.1). Girdide sözleşmede olmayan anahtar adıyla reddedilir, sessizce yok sayılmaz. StaticData lookup seçeneklerinin düz metni App.StaticLookup.* dil anahtarına çevrilir ve EN + TR satırı yazılır (LocalizeStaticLookupsAsync, lowcode.instructions.md §4.3.4.1). Dosyaya kullanıcının etkin talebi DevRequest olarak yazılır (sunucu doldurur). Wizard ekranı gibi yalnızca seed dosyalarını yazar; ekran DB Migrate ile kurulur ve çıkıştaki isPending o zamana kadar true kalır (model migrate'i beklemez; verify_wizard bekleyen dosyada install kontrolünde durur). replaceFileName doluysa güncellemedir (dosyalar yeniden yazılır, önceki kayıtları migrate kaldırır). İstek demo veri taşıyorsa (prompt'taki satırlar, ekran görüntüsü) wizard SeedData: true ile deploy edilir — ekranın SeedFilePath'i data/{ListFormCode}.json olur (SeedSync* bayrakları demo veriyle açılmaz; yalnızca istekte açıkça istenirse wizard'ın SeedSync: true cevabıyla) — satırlar aynı çağrıda seedRows ile bu dosyaya yazılır (alan adları doğrulanır, Guid anahtarı sunucu üretir); migrate ekranı kurduktan sonra satırları ekler (lowcode.instructions.md §0.10.1). Widget sorgularındaki her dil anahtarı tanımlı olmalı ya da wizard'ın Language.Texts alanında EN + TR verilmeli, yoksa deploy adlarıyla reddeder (ValidateWidgetLanguageKeysAsync). uploadUrl'süz dxImageUpload alanı base64 sakladığı için kolonu sınırlı boyluysa (katalogdaki karakter sınırı) deploy reddedilir; tablo kolonuna bağlı dxImageViewer alanı da reddedilir — görsel kolonunun editörü her zaman dxImageUpload'dır (ValidateImageColumnLengths). Yeni wizard'ın adına (= dosya adı) modül öneki eklenir: {ModuleId}_{Ad} (ListFormWizardDto.ResolveWizardName; aynı ekran adı başka modülde de geçebilir), çıkıştaki fileName gerçek adı verir. MenuOrder yok sayılır: yeni ekran kardeşlerinin sonuna, güncelleme mevcut yerine yazılır — lookup kaynağı ekran önce deploy edildiği için onu kullanan ekranın üstünde kalır.
delete_wizard Wizard Manager'daki silmeyle aynı: InsertedRecords + ekranın veri aynası, bağımlı wizard varsa reddedilir. dropTables: true ekranın tablosunu (yalnızca tasarımcı/create_table ile kurulmuş ve başka yerde kullanılmayan) CRUD endpoint'leri, sql/object ve crud dosyalarıyla düşürür; model bunu kullanıcı onayı olmadan göndermez (WizardTableCleaner, lowcode.instructions.md §4.5).
verify_wizard Deploy edilmiş ekranı veri yazmadan doğrular: menü kaydı ve URL'si, menünün istediği yetki tanımı (AbpPermissions), List yolunda ekran sorgusu (GetSelectAsync, tek satır, çağıranın yetkisiyle), Custom yolunda bileşen (etkin, rota = menü URL'si) ve seed kapanışı (ExportAnalysis uyarıları; engellemez). İsteğe bağlıdır, migrate sonrasında kullanılır; App.DevRequests döngüsünün adımı değildir — döngü Testing'de biter, kullanıcı elle test edip Staging'e alır (lowcode.instructions.md §0.11).
start_dev_request Geliştirme talebini Id ya da tam başlıkla üstlenir: yalnızca Ready to Start durumundakini In Progress durumuna alır, diğerlerini reddeder; talebin metnini döndürür. Açıklamaya yapıştırılmış base64 görseller (`data:image/png
get_dev_request Talebi durumuna dokunmadan, start_dev_request ile aynı biçimde (metin + analiz + resim içerikleri) okur; analizden önce ya da yarım kalan talebe dönmek için. App.DevRequests ister.
save_dev_request_analysis Backlog ya da Analysis durumundaki talebin analizini (HTML) Analysis kolonuna yazar — var olanı değiştirir — ve talebi Analysis durumuna alır; diğer durumları reddeder. Description'a dokunmaz. Modül dokümanına yazmaz; doküman üretimde açılır. Analizin standardı lowcode.instructions.md §0.12. .Write + App.DevRequests.Update ister.
add_dev_request_subtasks Talebin SubTasks listesine tamamlanmış satırlar ekler: model yalnızca başlığı verir, atanan kişi bağlanan kullanıcının adı (userName), bitiş tarihi bugün (yyyy-MM-dd) olarak sunucuda yazılır; var olan satırlar korunur. .Write + App.DevRequests.Update ister.
append_dev_request_note Talebin Analysis kolonunun sonuna HTML ekler (üretim sonuç notu); Description talebin yazıldığı hâliyle kalır. İkisi de update_row ile yazılmaz — açıklama base64 görsel taşır. .Write + App.DevRequests.Update ister.
list_components / get_component / save_component / delete_component Custom component'ler ICustomComponentAppService üzerinden; seed senkronu ve DataSources çözümü aynen çalışır. save_component ada göre ekler/günceller, tasarımcı belgesi (Props.visualDesigner, sourceMode ≠ code) taşıyan bileşene kod yazmayı reddeder — kod belgeden üretildiği için tasarımcıdaki ilk kayıtta ezilirdi.
get_module_doc / save_module_doc Modül dokümanı (configs/seeds/docs/modules/{Menü}.md, IModuleDocAppService). Kök menü kodu (App.Hr) dosyanın tamamını, ekran/dal kodu ya da wizard dosya adı yalnızca o dalın bölümünü okur/yazar. save_module_doc elle yazılan bölümleri kaydeder, "Ek — Ekran envanteri"ni sunucu üretir. Talebi update_row ile Testing'e almak, talebin wizard'larının bölümü tamamlanmadan (bölüm var, module-doc:todo kalmamış) reddedilir. Okuma App.Listforms.Wizard, yazma ayrıca Wizards.Update ister.
list_report_templates / get_report_template / save_report_category / save_report_template / delete_report_template Rapor kategorileri ve DynamicReport şablonları (McpReportAppService). Okuma depodan, ekranın okuma yetkisiyle yapılır. Yazma App.Reports.Categories / App.Reports.ReportTemplates ekranlarının C/U/D sözleşmesinden (IListFormDataAppService) geçer; seed senkronu aynen çalışır. Kayıt ada göre ekler/günceller. listFormCode verilirse HTML'deki {{AlanAdi}} yer tutucuları o ekranın alanlarına göre doğrulanır ve cevapta satır butonu (rowButton) döner (§15).

Timesheets. Talebin Timesheets listesini sunucu doldurur (IMcpDevRequestTimesheetRecorder): get_dev_request / start_dev_request talebi kullanıcının etkin talebi yapar (dağıtık önbellek, McpDevRequestActivityCacheItem, 12 saat); sonraki her MCP yazma işlemi (create_table, save_sql_object, generate_crud_endpoints, deploy_wizard, delete_wizard, save_component, delete_component, save_report_category, save_report_template, delete_report_template, save_module_doc, satır araçları, save_dev_request_analysis, append_dev_request_note, add_dev_request_subtasks) o talebe bir satır ekler; talebi açan start_dev_request yazmaz. Açıklama App.Mcp.Timesheet.* anahtarından Türkçe üretilir, süre önceki satırdan ya da talebin açıldığı andan ölçülür (en az 1 dakika, saat olarak), çalışan bağlanan kullanıcı, tarih bugündür. Etkin talep yoksa satır yazılmaz; bozuk bir Timesheets değeri işlemi geri almaz, yalnızca satır atlanır ve log'a düşer. Satır, aracın unit of work'ü commit edildikten sonra ayrı bir UoW'de yazılır (OnCompleted): satır araçları talebi Dapper bağlantısındaki transaction ile günceller ve EF'in ayrı bağlantıdan aynı satıra yazması o kilidi bekleyip zaman aşımına düşerdi.

Prompt'lar (McpPromptNames, McpDevelopPrompts). İstemcide komut olarak görünür; yalnızca metindir, yetki taşımaz. analyze_dev_request tek talebi, analyze_backlog_requests bütün Backlog kuyruğunu analiz edip Analysis durumuna alır (§0.12); process_dev_request tek talebi, process_ready_requests bütün Ready to Start kuyruğunu işler (§0.11). Hiçbiri adımları tekrar etmez, get_dev_guide ile lowcode.instructions.md ilgili bölümüne yönlendirir.

Geliştirme taleplerini çalıştırma. Komutun adı istemcideki sunucu adına göre değişir: aşağıdaki örnekler claude mcp add sozsoft ... ile eklenmiş sunucu içindir (sozsoft-local ile eklediysen mcp__sozsoft-local__... yaz). Her durumda talep yalnızca Ready to Start durumundaysa işlenir; değilse sunucu reddeder ve hiçbir şey üretilmez.

Ne istiyorsun Claude Code Claude.ai / Claude Desktop Düz metin (her istemci)
Tek talebi analiz et /mcp__sozsoft__analyze_dev_request <Id> + menüsü → sozsoft → analyze_dev_request <Id ya da "başlık"> talebini analiz et
Backlog'u analiz et /mcp__sozsoft__analyze_backlog_requests + menüsü → sozsoft → analyze_backlog_requests Backlog'daki talepleri analiz et
Tek talep /mcp__sozsoft__process_dev_request <Id> Mesaj kutusundaki + menüsü → sozsoft → process_dev_request, request alanına Id ya da tam başlık <Id ya da "başlık"> talebini işle
Bütün kuyruk /mcp__sozsoft__process_ready_requests + menüsü → sozsoft → process_ready_requests Ready to Start durumundaki talepleri sırayla işle
Önce kuyruğu görmek — — Ready to Start durumundaki talepleri Id'leriyle listele
/mcp__sozsoft__process_dev_request 3f2a9c1e-5b7d-4e2a-9f10-6c8d2b4a1e77
/mcp__sozsoft__process_ready_requests
  • Id nereden gelir: App.DevRequests ekranında Id kolonu gizlidir. Kolon seçiciden aç ya da Claude'dan "Id'leriyle listele" ile iste.
  • Başlıkla çağırmak: Claude Code komut argümanlarını boşluktan böler, bu yüzden boşluklu başlıkta komut yerine Id ya da düz metin kullan. Başlık birden fazla talebe uyuyorsa sunucu Id ister.
  • Düz metin de aynı yoldan geçer: sunucu talimatları modeli start_dev_request'e yönlendirir, durum şartı yine sunucuda uygulanır.

Yetki modeli: MCP yetki vermez. Kullanıcı MCP'de uygulamadaki yetkisini taşır; MCP'nin iki yetkisi yalnızca kapıdır. Bir çağrı üç katmandan geçer ve her katman ayrı reddeder:

  1. Sunucu kaydı (AllowedTools, Tenants) — o kiracıda hangi araçların var olduğu. Kiracıdaki herkes için aynıdır.
  2. MCP kapısı (.Invoke, .Write) — kullanıcının MCP'yi okumak / değiştirmek için kullanabilir olup olmadığı. Rol/kullanıcı bazındadır.
  3. Uygulama yetkisi — işlemin kendisi: ekranın C/R/U/D yetkisi, Wizard, Custom Components, SQL Query Manager. Kullanıcı uygulamada yapamadığını MCP'de de yapamaz.

MCP yetkileri — ne açar, sınırı ne

Yetki Kim alabilir Ne açar Açmadığı / sınırı
App.DeveloperKit.McpServers Host, kiracı MCP Servers ekranını görme (.Create/.Update/.Delete/.Export/.Import ekran işlemleri) /mcp ucuna erişim vermez
App.DeveloperKit.McpServers.Invoke Host, kiracı /mcp bağlantısı ve bütün okuma araçları: server_info, list_lists, describe_list, search_schema, query_list; host'ta ayrıca get_dev_guide, list_wizards, get_wizard, verify_wizard, get_dev_request, list_components, get_component Hiçbir şey yazmaz. Okuma da ekran yetkisine bağlıdır: R yetkisi olmayan liste görünmez, sorgulanamaz. Ham SQL (run_select, list_queries, run_query) ayrıca App.SqlQueryManager ister
App.DeveloperKit.McpServers.Write Host, kiracı MCP üzerinden her değişiklik. Kiracıda: insert_row, update_row, delete_row. Host'ta: bunlara ek olarak create_table, save_sql_object, generate_crud_endpoints, deploy_wizard, delete_wizard, save_component, delete_component, save_report_category, save_report_template, delete_report_template, save_module_doc, start_dev_request, save_dev_request_analysis, append_dev_request_note, add_dev_request_subtasks Kendi başına bir şey yazdırmaz: satır araçları ekranın C/U/D yetkisini, geliştirme araçları Wizard / Custom Components / SQL Query Manager / App.DevRequests.Update yetkisini ayrıca ister. Kiracıda geliştirme araçlarını açmaz — kiracı bağlamında bu araçlar yetkiden bağımsız kapalıdır (McpToolNames.TenantTools). Invoke olmadan işe yaramaz

Araç → gereken yetkiler (Invoke hepsinde aranır, tabloda tekrar yazılmadı)

Araç Kiracı MCP kapısı Uygulama yetkisi
server_info, list_lists, describe_list, search_schema ✓ — Listelenen her ekran için PermissionJson.R
query_list ✓ — Ekranın R yetkisi
run_select ✓ — App.SqlQueryManager
list_queries, run_query — — Nesne: App.SqlQueryManager; Custom Endpoint: App.DeveloperKit.CustomEndpoints.Get + endpoint'in kullanıcı/rol listesi
insert_row / update_row / delete_row ✓ .Write Ekranın C / U / D yetkisi
get_dev_guide — — —
list_wizards, get_wizard — — App.Listforms.Wizard
verify_wizard — — App.Listforms.Wizard; seed kapanışı için .Export (yoksa o kontrol atlanır, doğrulama düşmez)
deploy_wizard — .Write App.Listforms.Wizard + .Create (yeni) / .Update (replaceFileName)
delete_wizard — .Write App.Listforms.Wizard + .Delete; dropTables için ayrıca App.SqlQueryManager
create_table, save_sql_object — .Write App.SqlQueryManager
generate_crud_endpoints — .Write App.SqlQueryManager.CrudEndpoints
list_components, get_component — — App.DeveloperKit.CustomComponents
save_component — .Write App.DeveloperKit.CustomComponents.Create (yeni) / .Update
delete_component — .Write App.DeveloperKit.CustomComponents.Delete
list_report_templates, get_report_template — — App.Reports.ReportTemplates
save_report_category — .Write App.Reports.Categories.Create (yeni) / .Update
save_report_template — .Write App.Reports.ReportTemplates.Create (yeni) / .Update
delete_report_template — .Write App.Reports.ReportTemplates.Delete
start_dev_request — .Write App.DevRequests.Update
get_dev_request — — App.DevRequests
save_dev_request_analysis — .Write App.DevRequests.Update
append_dev_request_note — .Write App.DevRequests.Update
add_dev_request_subtasks — .Write App.DevRequests.Update

Kaynak: MCP kapısı McpToolNames.RequiredPermissions, kiracı kümesi McpToolNames.TenantTools, arkadaki servisin attribute ile istediği yetkiler McpToolNames.ServicePermissions. Invoke her MCP servisinin sınıf düzeyindeki [Authorize] attribute'undadır; /mcp ucu yalnızca kimlik doğrulama ister (ucun kendisinde aranırsa kullanıcı nedeni yazmayan bir 403 alır). Araç listesi aynı kurallarla süzülür (GetAvailableToolsAsync), çağrı anında kontrol tekrarlanır.

Yetkisi olmayan çağrı ne görür. SDK araç hatasının mesajını yalnızca McpException ise iletir; diğer her istisna "An error occurred invoking '…'" olarak gider. McpToolErrorTranslator (çağrı ve araç listesi filtresi, McpHttpApiModule) iki tür hatayı çevirir:

  • Yetki reddi → App.Mcp.Error.PermissionDenied: "'deploy_wizard' için gereken yetkiniz yok: Wizard (App.Listforms.Wizard). Yöneticinizden bu yetkiyi isteyin." Eksik yetkiler aracın bilinen yetkileri (McpToolNames.KnownPermissionsOf) ve reddin taşıdığı yetki adı kullanıcının yetkileriyle karşılaştırılarak bulunur. Satır ve query_list araçları ekranın yetkisini adıyla önceden ister (IMcpListFormAccessFilter.GetPermissionNameAsync), bu yüzden ekran yetkisi de adıyla görünür. Eksik yetki belirlenemezse PermissionDeniedUnspecified döner.
  • İş kuralı hatası (UserFriendlyException: kiracıya kapalı araç, AllowedTools dışı araç, start_dev_request durum reddi…) → yerelleştirilmiş mesajı olduğu gibi.

Başka istisnalar çevrilmez; SQL ve yığın ayrıntısı istemciye sızmaz. Tek istisna run_select'tir: sağlayıcı hatası (DbException, örn. tabloda olmayan kolon) App.Mcp.Error.SelectFailed ile mesajıyla döner ki model sorguyu düzeltebilsin — araç zaten App.SqlQueryManager ister, hata metni yeni bilgi sızdırmaz (McpQueryAppService).

Geliştirme rehberi nereden okur. get_dev_guide depodaki .github/instructions/lowcode*.instructions.md dosyalarını runtime'da okur; Claude Code ile MCP istemcisi aynı sözleşmeyi görür. Klasör App:DevGuidePath ayarından (konteynerde /etc/api/dev-guide, compose'da .github/instructions salt okunur mount), boşsa çalışma dizininden yukarı .github/instructions aranarak bulunur. İmaja gömülmez: API build bağlamı api/ olduğu için .github imaja girmez, rehber değiştiğinde imaj derlenmez.

Şema keşfi nereden okur. Tablo ve sütun bilgisi canlı katalogdan değil, Sas_H_DbTableColumn tablosundaki JSON veri sözlüğünden gelir — sözlük dil anahtarlarından çözülmüş başlıkları ve lookup ilişkilerini birlikte taşıdığı için modelin gördüğü adlar kullanıcının ekranda gördüğü adlarla aynı olur. Sözlük dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns prosedürüyle kiracı adı başına üretilir; sözlük yoksa araçlar tablo döndürmez ve server_info bunu bildirir.

OAuth (Claude / ChatGPT connector'ları). Connector formuna yalnızca ad ve URL girilir; kimlik doğrulamayı istemci MCP OAuth akışıyla kendisi yürütür:

  1. /mcp token'sız isteğe 401 + WWW-Authenticate: Bearer resource_metadata="…" döner (McpAuthenticationChallengeMiddleware).
  2. İstemci /.well-known/oauth-protected-resource/mcp (RFC 9728) belgesinden yetkilendirme sunucusunu (AuthServer:Authority) öğrenir, /.well-known/oauth-authorization-server (OpenIddict keşif belgesi, registration_endpoint eklenmiş) belgesini okur.
  3. /connect/register (RFC 7591, McpOAuthClientManager):
    • İstenen geri dönüş adresleri bir bilinen istemcide kayıtlıysa onun kimliği döner: Platform_Mcp (Claude, ChatGPT; onay ekranı çıkmaz) ya da Platform_Mcp_Local (yerel mcp-remote proxy'si, 127.0.0.1 callback'i; açık onaylı). Adresler DbMigrator ayarı OpenIddict:Applications:{ClientId}:RedirectUris'tedir; ayarı olmayan istemci seed edilmez.
    • Değilse (Cursor, VS Code, Claude Code, başka bir AI istemcisi) dinamik istemci üretilir: mcp_ önekli, PKCE zorunlu, açık onaylı OpenIddict uygulaması. Aynı adres kümesi aynı uygulamaya bağlanır — kayıt kilit altında ve kendi UoW'sinde commit edilir, masaüstü istemcisinin aynı anda başlattığı proxy süreçleri ikinci kayıt açamaz; tavan McpConsts.MaxDynamicClients. Kabul edilen adresler: HTTPS, localhost HTTP (RFC 8252), uygulama şemaları (cursor://…); javascript: vb. reddedilir. Onay ekranındaki ad istemcinin beyanı + geri dönüş adresinin sahibidir (ad doğrulanamaz).
  4. Kullanıcı platformun giriş ekranına (/Account/Login, kiracı seçimi dahil) yönlenir; authorization code + PKCE ile token alınır. Kiracı ve yetki giriş yapan kullanıcıdan gelir.

Üç ayrıntı bilinçlidir:

  • Yayıncı (issuer) AuthServer:Authority'den sabitlenir; ters vekil arkasında istekten türetilirse http:// olur ve istemci keşif belgesini reddeder. Korumalı kaynak belgesindeki authorization_servers değeri bu issuer ile birebir aynıdır (sonda / dahil, McpOAuthOptions.AuthorizationServer); RFC 8414 §3.3'ü uygulayan istemci (mcp-remote) farkta IssuerMismatchError ile durur.
  • MCP istemcisinin gönderdiği RFC 8707 resource parametresi yok sayılır (McpOAuthServerConfiguration): OpenIddict kayıtsız kaynağı reddeder ya da token kitlesini /mcp adresine daraltır, API ise Platform kitlesini doğrular.
  • MCP istemcilerinin (McpConsts.IsMcpClient) token'ları kullanıcı oturumuna bağlanmaz (PlatformTokenController). Tek oturum kuralı her girişte diğer oturumları sildiği için, bağlansaydı connector her token yenilemesinde kullanıcının web oturumunu düşürürdü.
  • MCP istemcilerinin yenileme token ömrü uygulama bazında 30 gündür (McpConsts.RefreshTokenLifetime, OpenIddict uygulama ayarı) ve her kullanımda yenilenir; web istemcisinin dakikalık ömrü etkilenmez. Kullanıcı 30 gün hiç soru sormazsa bağlantıyı yeniden kurar.

Bağlantı sunucu gerektirir: connector formu bağlantıyı istemci sağlayıcının sunucularından kurar, localhost erişilemez. Forma https://api.sozsoft.com/mcp yazılır.

Bağlanma (token ile, CLI).

# sunucu
claude mcp add sozsoft --transport http https://api.sozsoft.com/mcp \n  --header "Authorization: Bearer <access_token>"

# localhost (dotnet run / IIS Express)
claude mcp add sozsoft-local --transport http https://localhost:44344/mcp \n  --header "Authorization: Bearer <access_token>"

Ters vekilde /mcp ayrı bir blokta karşılanır (configs/deployment/configs/nginx.conf): Streamable HTTP yanıtları SSE akışıdır, location / altındaki tamponlama akışı bekletir.

Şema tuzağı. İstek vekilden HTTP olarak gelir. issuer ve registration_endpoint yapılandırmadan sabitlenir (AuthServer:Authority), ama authorization_endpoint ve token_endpoint adreslerini OpenIddict istekten türetir. Uygulama X-Forwarded-Proto başlığını işlemezse bu iki adres keşif belgesine http:// olarak yazılır ve OAuth 2.1 istemcileri (MCP connector'ları) TLS'siz uç kabul etmediği için bağlantı kurulamaz — istemci tarafında görülen hata genellikle "please register the OAuth client" olur.

Çözüm iki parçalıdır ve ikisi birlikte olmak zorundadır:

  • API konteynerinde ASPNETCORE_FORWARDEDHEADERS_ENABLED=true (docker-compose-app.yml)
  • nginx'te her location bloğunun X-Forwarded-Proto $scheme göndermesi. location / bunu proxy_params üzerinden yapar; /mcp, /videoroomhub, /messengerhub ve /notificationhub kendi blokları olduğu için başlığı ayrıca set eder. Bir blok atlanırsa istemcinin gönderdiği başlık olduğu gibi geçer ve şema taklit edilebilir hale gelir.

Doğrulama:

curl -s https://api.sozsoft.com/.well-known/oauth-authorization-server \
  | tr ',' '\n' | grep -E 'issuer|authorization_endpoint|token_endpoint|registration_endpoint'

Dördü de https:// olmalıdır.

Yetkiler. Ne açtıkları ve sınırları yukarıdaki "Yetki modeli" tablosundadır. İkisi de MultiTenancySide = Both'tur. Eskiden geliştirme için ayrı olan .Develop yetkisi .Write ile birleştirildi; mevcut veritabanında atamalar migrate sırasında .Write'a taşınır ve tanım silinir (McpDevelopPermissionMergeDataSeedContributor).

AllowedTools ile yetkiler farklı eksenlerdir: sunucu kaydı bir kiracıda hangi araçların var olduğunu belirler ve o kiracının bütün kullanıcıları için aynıdır; yetkiler kimin kullanabileceğini rol/kullanıcı bazında belirler. Örneğin delete_row açık bir kayıtta yalnızca .Write verilmiş rol MCP ile silebilir, diğerleri aynı bağlantıyla yalnızca okur.


14. Arka Plan İşleri

İki motor birlikte kullanılır: Hangfire (kalıcı iş deposu, default ve platform kuyrukları) ve ABP Background Workers (yinelenen işler). Yönetim /admin altındaki Background Worker ekranlarından yapılır; yetkiler App.BackgroundWorkers.RecurringJobs ve .Jobs.

Worker tipi İşlevi
MailQueueWorker Mail kuyruğunu işler.
SqlWorker Zamanlanmış SQL çalıştırır.
NotificationWorker Bildirim kurallarını değerlendirir ve gönderir.
SessionCleanupWorker Süresi dolmuş oturumları temizler.
BackupWorker Veritabanı yedeği alır ve eski yedekleri temizler.
ProbationReminderWorker Deneme süresinin bitmesine 15 gün kalan çalışanlar için DenemeSuresiBitiyor bildirim tipinin kurallarını işletir (Scope: All, her gün 08:00).

Kapsam (Scope). İş kaydı her zaman host tablosundadır (Sas_H_BackgroundWorker); işin gövdesinin hangi kiracı bağlamında kaç kez çalışacağını Scope belirler (WorkerScopeEnum): Host yalnızca host bağlamında bir kez, Tenant her kiracı için bir kez, All önce host sonra her kiracı için. Her kapsam ayrı bir Hangfire işidir (BackgroundWorkerInitializer): host {WorkerType}:{Name}, kiracı {WorkerType}:{Name}#{TenantId}. PlatformBackgroundWorker.StartAsync tek kapsam çalıştırır, kilidi de kapsam başına alır — bir kiracının hatası ya da yavaşlığı diğerlerini durdurmaz ve Jobs ekranı kiracı başına süre, durum ve hata gösterir (Tenant kolonu; ad Sas_H_BackgroundWorker_JobFlow içinde iş adının # ekinden AbpTenants'a çözülür). Kiracı adı sonradan değişebildiği için iş adına kimlik yazılır. Kiracı eklenip silindiğinde zamanlama yeniden üretilir (BackgroundWorkerTenantHandler). BeforeSp/AfterSp de kapsam başına çalışır, çünkü dokundukları veri kiracıya aittir. Bildirim kuyruğu kiracı tablosunda olduğu için NotificationWorker işi All kapsamıyla gelir (data/App.BackgroundWorkers.RecurringJobs.json).

Hangfire panosu geliştirmede serbest, üretimde AbpHangfireAuthorizationFilter ile korumalıdır (/hangfire).


15. Raporlama

  • DevExpress Reporting: Web Document Viewer (/admin/reports/:report/view/...) ve Report Designer (/admin/reports/:report/design/...).
  • Rapor deposu: ReportTemplate ve ReportCategory varlıkları (data/App.Reports.{ReportTemplates,Categories}.json, seed senkronu açık). Tasarımcıda kaydedilen .repx düzenleri CustomReportStorageWebExtension ile sunucudaki PredefinedReports/Reports klasörüne yazılır.
  • DynamicReport şablonları (Raporlar kategorisi): HtmlContent HTML'i A4'e basılır (/admin/reports/DynamicReport/view/{ŞablonId}). Ekran satırından açılınca (.../view/{ŞablonId}/{ListFormCode}?recordId=@Id) {{AlanAdi}} yer tutucuları kaydın form raporundaki metniyle doldurulur (DynamicFormReport.GetRecordTextsAsync; ekran okuma yetkisi + kiracı filtresi, değerler HTML kodlanır). Kiracıda bulunmayan şablon host'tan okunur. MCP araçları §13.9.
  • Dinamik raporlar (HttpApi.Host/PredefinedReports): ListForm tanımından otomatik üretilen grid, form ve ağaç raporları — grid durumunu (sütun sırası, gruplama, filtre) rapora yansıtır. Form raporu (DynamicFormReport) alt formları ekrandaki sekme sırasıyla basar: List sekmesi grid alt raporu, Form sekmesi (aynı satırı bölen Id → Id sekmeleri dahil) form alt raporu olur; iç içe form sekmeleri en fazla 3 seviye iner.
  • Linux konteynerlerde font sorunlarını önlemek için DevExpressFontConfiguration devrededir.

16. Dosya Yönetimi ve CDN

  • ABP BlobStoring, dosya sistemi sağlayıcısıyla App:CdnPath altına yazar; dosyalar App:CdnUrl üzerinden servis edilir.
  • FileManagementAppService: listeleme, yükleme, indirme, kopyalama, taşıma, yeniden adlandırma, silme.
  • UI: /admin/files (views/admin/files/FileManager.tsx) — breadcrumb, çoklu yükleme, önizleme.
  • Avatar, aktivite görselleri, içe aktarma dosyaları ve veritabanı yedekleri ayrı konteynerlerde tutulur.

17. Gerçek Zamanlı Özellikler

  • Messenger: SignalR hub'ı /messengerhub. Konuşma ve mesaj varlıkları veritabanında; Redis etkinse backplane olarak kullanılır. Query string ile gelen access_token, hub bağlantısı için Authorization başlığına çevrilir.
  • Video oda: Oda oluşturma/başlatma/bitirme, katılımcı yönetimi, sohbet, doküman ve ekran paylaşımı panelleri; TURN sunucusu configs/deployment/configs/turnserver.conf ile yapılandırılır.
  • Global arama: GlobalSearchAppService üzerinden çapraz modül arama.

18. Frontend Mimarisi

18.1. Route ve bileşen çözümü

Route kayıtları (DB)
   ├─ componentPath ──► fiziksel view (React.lazy)
   └─ CustomComponent ──► @babel/standalone ile runtime derleme ──► ComponentContext
                                     │
                              DynamicRouter ──► ProtectedRoute ──► PermissionGuard ──► PageContainer

ROUTES_ENUM (src/routes/route.constant.ts) statik yol sözleşmelerini tutar; iş modülleri için (supplychain, maintenance, warehouse, projects, hr, crm, mrp, accounting) ayrılmış yol desenleri de buradadır. Bu alanların ekranları, fiziksel React sayfaları yerine ListForm konfigürasyonu ile üretilir.

18.2. Durum yönetimi

  • easy-peasy global store: auth, theme, locale, abpConfig, admin, client, base. Oturum ve tercih verileri localStorage yerine store üzerinden yönetilir (stateSync.ts).
  • React Query: sunucu verisi önbellekleme.
  • Context'ler: ComponentContext (runtime bileşen kaydı), DialogContext, ScrollContext.

18.3. Tema ve lokalizasyon

  • Tailwind tabanlı tasarım sistemi (components/ui) + DevExtreme temaları; açık/koyu mod ve tema yapılandırıcı (ThemeConfigurator).
  • Varsayılan mod koyudur. proxy/theme/theme.config.ts: mode ve navMode dark, DevExtreme stili dx.material.blue.dark.compact. Kullanıcı tercihi store üzerinden saklanır; bu yalnızca ilk açılış varsayılanıdır.
  • Dil metinleri veritabanından gelir (Sozsoft.Languages); dil seçici header'dadır. Tenant bazlı çeviri geçersiz kılma desteklenir.
  • Layout seçenekleri: dikey menü, yatay menü, yığılmış yan menü, mobil navigasyon.

18.4. PWA ve sürüm yönetimi

  • Service worker kurulumu yalnızca kabuğu indirir (index.html + entry JS/CSS + eager vendor chunk'ları, workbox precache manifest'i); kabuk zorunludur. Bileşen ısıtması kurulumdan ayrıdır ve yalnızca oturum açık kullanıcı içindir: store, application-configuration her geldiğinde protected fiziksel rotalardan kullanıcının yetkili olduklarını (PermissionGuard ile aynı ölçüt) setWarmupRoutes ile bildirir, uygulama ENSURE_WARMUP mesajıyla bu rotaları worker'a yollar. Worker build'de üretilen dist/warmup.json'daki routes eşlemesinden (view modülü → chunk + statik import zinciri + CSS) yalnızca o dosyaları indirir. Ziyaretçi, public sayfa ve giriş ekranı hiçbir bileşen indirmez; ekranın içinden lazy açılan parçalar (diyalog, editör) ve runtime bileşenleri ilk kullanımda runtime cache'e alınır. Isıtma best-effort'tur (inemeyen dosya atlanır, süre bütçesi aşılırsa kalanlar runtime cache'e kalır) ve her açılışta eksikleri kaldığı yerden tamamlar. DevExtreme temaları (~33 MB), font ve görseller runtime cache'tedir.
  • registerType: 'prompt' — yeni sürüm indirilir, indirme bitince otomatik devreye alınır (views/version/swRegistration.ts, sw.ts, AppUpdateDialog.tsx).
  • Sürüm bilgisi backend'dedir. Sas_H_ChangeLog tablosu tek kaynaktır; istemci sürüm numarasını ve son sürümün notlarını application-configuration yanıtındaki extraProperties.changeLogs alanından okur (PlatformApplicationConfigurationContributor), tam listeyi ise /api/app/change-log endpoint'inden alır. Ayrı bir version.json isteği yoktur. Changelog /admin/changeLog ekranında gösterilir; sürüm numarası son görülenden farklıysa kullanıcı giriş sonrası oraya yönlendirilir (tetikleyici bundle değil, sürümdür — yalnızca yeni bir git tag'i eklenmiş olsa da çalışır).
  • Sürüm notu akışı — DbMigrator gerektirmez: configs/change-logs/generate-change-logs.sh deploy sırasında (deploy/app.sh) git tag'lerinden configs/change-logs/change-logs.json üretir, dosya API konteynerine /etc/api/change-logs altına salt okunur mount edilir ve ChangeLogStartupService uygulama açılışında tabloyu bu dosyayla eşitler. Dosya imaja gömülmez: api build context'i içindeyken her yeni tag Dockerfile'daki COPY . . katmanını geçersiz kılıp imajı gereksiz yere yeniden derletiyordu; artık kod değişmediyse imaj cache'ten gelir. Yeni sürüm için tag atıp deploy etmek yeterlidir. Notlar annotated tag mesajından okunur (git tag -a 1.2.0 -m "..."), her satır bir madde.
  • Tablo git tag listesinin aynasıdır: yeni tag eklenir, mesajı düzeltilen tag güncellenir, git'ten silinen tag tablodan da (hard delete ile) kaldırılır. Bu yüzden Sas_H_ChangeLog elle düzenlenmemelidir — ilk deploy'da geri alınır. Dosya okunamaz veya boşsa senkronizasyon hiçbir şey yapmaz, tablo boşaltılmaz.
  • Deploy algılaması service worker'ın kendi registration.update() kontrolüne dayanır: herhangi bir chunk değiştiğinde entry hash'i, dolayısıyla index.html ve sw.js içindeki precache manifest'i de değişir.

19. API ve Swagger

  • Swagger UI, API kökünde yayınlanır; OAuth2 ile "Authorize" düğmesi üzerinden token alınabilir.
  • Endpoint'ler ABP conventional controller mekanizmasıyla uygulama servislerinden üretilir:
Namespace Kök yol
Sozsoft.Platform.ListForms.Administration /api/admin/...
Diğer Sozsoft.Platform.* servisleri /api/app/...
Modül servisleri (Languages, Settings, MailQueue, Notifications, SqlQueryManager) Modül kendi kök yolunu kullanır
  • DevExpress raporlama controller'ları (CustomReportDesigner, CustomWebDocumentViewer, QueryBuilder) Swagger dokümanından hariç tutulur.
  • Yanıtlar Brotli/Gzip ile sıkıştırılır; X-Correlation-Id başlığı istemciye açılır.
  • Hata kodları: PlatformConsts.AppErrorCodes (Error:0001 yetkisiz, App.NoResults kayıt yok, Error:0003 parametre geçersiz, Error:0005 iç hata).

Token örneği (yer tutucularla):

POST /connect/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=password
&username=<KULLANICI>
&password=<PAROLA>
&client_id=Platform_PublicApi
&scope=offline_access%20Platform

20. Dağıtım (Deployment)

configs/deployment altında üç ortam için compose dosyaları ve numaralandırılmış scriptler bulunur. Benzer adlı configs/docker ile karıştırılmamalı: docker/ yalnızca yerel geliştirmede veri katmanını (SQL/PostgreSQL/Redis) ayağa kaldırır, deployment/ ise sunucudaki kurulumdur.

Dosya Amaç
docker-compose-data.yml Redis, SQL Server / PostgreSQL, yedek volume'ları
docker-compose-app.yml / .dev.yml / .production.yml API, UI, migrator, nginx
docker-compose-devops.yml Forgejo (CI/CD), Rocket.Chat, n8n, Ollama, Open WebUI, coturn
Dockerfile, api/*.Dockerfile, ui/Sozsoft.Platform.Ui.Dockerfile İmaj tanımları

Servis yapılandırma dosyaları tek klasörde, configs/deployment/configs/ altındadır — compose dosyaları buradan mount eder, ikinci bir kopya tutulmaz:

Dosya Kullanan servis
nginx.conf Ters vekil: tüm *.sozsoft.com vhost'ları, SSL sonlandırma, /mcp bloğu
redis.conf / redis.7.conf Redis (dağıtık cache, sürüme göre)
turnserver.conf coturn — WebRTC TURN/STUN (bkz. §Messenger)
elasticsearch.yml Elasticsearch (opsiyonel log/arama katmanı)

Alt alan adı → port eşlemesi

nginx.conf içindeki vhost'lar host üzerinde yayınlanan portlara bakar; yeni bir servis eklerken portun boş olduğu buradan doğrulanır.

Alan adı Port Servis
devops.sozsoft.com 3000 Forgejo
chat.sozsoft.com 3001 Rocket.Chat
sozsoft.com + tenant alt alan adları 3002 UI (React)
ollama.sozsoft.com 3003 Open WebUI — sohbet arayüzü
ollama.sozsoft.com/ollama-api/ 11434 Ollama ham API'si (basic auth)
ai.sozsoft.com 5678 n8n
api.sozsoft.com 8080 API (/mcp, /videoroomhub, /messengerhub, /notificationhub dahil)
cdn.sozsoft.com 8081 CDN

Open WebUI kendi backend'ini /api/... altında sunduğu için Ollama'nın ham API'si /ollama-api/ önekine alınmıştır; iki uç aynı önekte yaşayamaz.

Basic auth yalnızca /ollama-api/ bloğundadır — Ollama'nın kendi kimlik doğrulaması yoktur. Sohbet arayüzünün önüne konmaz: Open WebUI socket.io üzerinden WebSocket açar, tarayıcı WS upgrade isteğine basic auth kimliğini eklemez ve bağlantı 401 döngüsüne girer. Arayüzün güvenlik sınırı uygulamanın kendi girişidir; bu yüzden kurulumdan hemen sonra yeni kayıt alımının kapatılması bir tercih değil, kurulumun parçasıdır (Settings → Admin → General → Enable New Sign Ups). Daha sıkı bir sınır gerekiyorsa vhost'a allow/deny ile IP kısıtı eklenir; basic auth bu arayüz için bir seçenek değildir.

Compose tarafında DEFAULT_USER_ROLE=pending ikinci bir emniyet katmanıdır: ilk kaydolan admin olur, sonrakiler onay bekleyen role düşer. Bu değer ve ENABLE_SIGNUP Open WebUI'nin PersistentConfig değişkenlerindendir — yalnızca ilk açılışta env'den okunur, sonrasında veritabanındaki değer geçerlidir; yani env'i sonradan değiştirmek yerine arayüzdeki ayar kullanılır.

GPU olmayan 8 GB'lık DevOps makinesinde Ollama OLLAMA_MAX_LOADED_MODELS=1 ile sınırlıdır ve Open WebUI'nin gömme/erişim hattı kapalıdır (BYPASS_EMBEDDING_AND_RETRIEVAL): varsayılan RAG hattı ayrıca bir gömme modeli indirir, bu makinede bellek bütçesi yoktur. Yüklenen dosyanın metni doğrudan bağlama girer.

Script sırası (configs/deployment/scripts):

1-devops.sh              → DevOps makinesi (Forgejo, Rocket.Chat, n8n, Ollama, Open WebUI)
2-data-postgres.sh /
3-data-sql.sh            → Veri katmanı
4-production-build.sh    → İmaj build
5-production-deploy.sh   → Yayın
6-production-migrator-true.sh → Migration çalıştırma
7-setup_ssl.sh           → Sertifika
8-dev-build.sh / 9-dev-deploy.sh / 10-dev-migrator-true.sh → Dev ortamı

Migrator servisi SEED ortam değişkeni ile çalışır ve compose dosyasında SEED=${SEED:-true} olarak tanımlıdır: değişken verilmezse seed çalışır. Seed dosyaları imaja gömülmez; depodaki configs/seeds klasöründen mount edilir (App:SeedsPath), böylece bir seed değiştiğinde imaj build etmek gerekmez.

Ayrıntılı kurulum notları: configs/deployment/README.md, Readme_Production.md, Readme_Devops.md.

Bu dokümanlarda geçmişte örnek amaçlı bırakılmış kimlik bilgileri bulunabilir; üretimde tümü döndürülmeli ve secret yöneticisine taşınmalıdır.


21. Geliştirme Standartları ve Sık Kullanılan Komutlar

21.1. Çözüm kuralları

  1. Her istek şu sırayla değerlendirilir: (1) ListForm konfigürasyonu → (2) SQL Query Manager + Custom Endpoint → (3) Dynamic Service → (4) kod değişikliği (son çare, gerekçe zorunlu).
  2. Standart iş ihtiyaçları için yeni özel React sayfası/bileşeni geliştirilmez.
  3. Her öneri tenant ve yetki tasarımını içerir.
  4. Platform yetkilendirme desenleri atlanmaz.
  5. Secret, tenant id veya bağlantı dizesi koda gömülmez.

21.2. Frontend

cd ui
npm start          # geliştirme sunucusu
npm run build      # üretim derlemesi (metadata + version + vite build)
npm run lint       # ESLint
npm run format     # prettier --write + eslint --fix

21.3. Backend

cd api
dotnet build
dotnet run --project src/Sozsoft.Platform.HttpApi.Host
dotnet format --include .\modules\Sozsoft.Notifications\ --folder

# Kod standardı kapısı (ölü kod / stil analizörleri)
dotnet build Sozsoft.Platform.sln --no-incremental `
  -p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true
dotnet format Sozsoft.Platform.sln --diagnostics IDE0005,IDE0161 --severity warn

# Migration (EntityFrameworkCore projesi içinde)
dotnet ef migrations add <Ad>
dotnet ef database update
dotnet ef migrations remove

.NET/ABP kod standardı (hedef sürümler, nullable politikası, ölü kod kuralları, modern C# kullanımı, ABP katman sınırları) bağlayıcı olarak .github/instructions/dotnet.instructions.md dosyasındadır. Standart derlemede zorlanır: api/Directory.Build.props analizörleri açar, api/.editorconfig kural şiddetlerini tanımlar; bu iki dosya standardın tek kaynağıdır.

Yeni ABP modülü eklemek için: abp new Sozsoft.<Modul> -t module --no-ui -m none --database-provider ef

21.4. Seed dosyaları

İki ayrı seed yüzeyi vardır ve karıştırılmamalıdır.

1. Depo içi (kod ile taşınan) seed'ler — api/src/Sozsoft.Platform.DbMigrator/Migrations/. Platformun kendi ekranları buradadır (dil metinleri configs/seeds/data/App.Languages.*.json); değişiklikleri derlenip yayınlanır. Yeni bir modülün nasıl kurulacağını öğrenmek için başvurulacak dosyalar:

  • ListFormSeeder_Saas.cs, ListFormSeeder_Administration.cs, ListFormSeeder_Hr.cs — menü modülüne göre ayrılmış ListForm + alan tanımları (İK ekranları ListFormSeeder_Hr.cs'te)
  • WizardDataSeeder.cs, CrudDataSeeder.cs — aşağıdaki runtime seed dosyalarını okuyup uygulayan seeder'lar
  • ListFormSeedDataApplier, CrudEndpointSeedApplier (Domain/Data/) — uygulama kuralının tek yeri; hem migrate/seed akışı hem wizard import'unun kapanışı aynı servisleri çağırır

2. Runtime'da üretilen seed'ler — configs/seeds/. Wizard, Custom Components ekranı/Visual Designer, CRUD Endpoint Manager ve SeedFilePath tanımlı ListForm ekranları çalışma zamanında burayı yazar; veritabanı silinip yeniden oluşturulduğunda aynı dosyalar geri yüklenir. Klasör tektir (host ve kendi veritabanı olan her tenant aynı dosyalarla kurulur) ve SeedPathResolver üzerinden çözülür:

configs/seeds/
├── wizard/               # {Ad}.json                  → WizardDataSeeder
├── crud/                 # {EntityName}.json          → CrudEndpointSeedApplier
├── docs/modules/         # {Menü}.md                  → ModuleDocManager (seed edilmez)
├── data/                 # {ListFormCode}.json ya da {ListFormCode}/{anahtar}.json → ListFormSeedDataApplier
│                         #   App.Menus.{Routes,Module,Menu}.json — route/modül/menü ağacı
│                         #   Abp.Identity.Permission{Groups,s}.json — yetki tanımları
│                         #   App.Definitions.{CountryGroup,Country,City,District}.json — adres zinciri
│                         #   App.Definitions.{Currency,ContactTitle,AiBot,TerminationReason}.json — para birimi, hitap, AI bot, SGK çıkış kodu
│                         #   App.Hr.{SkillType,SkillLevel,Skill}.json — İK yetkinlik tanımları
│                         #   App.Hr.{Department,JobPosition}.json — İK departman → pozisyon (BranchId ile şubeye bağlı)
│                         #   App.Hr.Intranet.*.json — İK › Intranet (duyuru, anket, sosyal akış, etkinlik)
│                         #   App.Hr.{CandidateSource,RejectionReason,EvaluationCriterion}.json — İK › Tanımlar altındaki işe alım tanımları
│                         #   App.Hr.PerformanceRating.json — İK › Tanımlar › Performans Dereceleri (5 kademeli ölçek)
│                         #   App.Notifications.Notification{Templates,Types,Rules}.json — bildirim şablonu, tipi → kuralı
│                         #   App.Languages.{Language,LanguageText}.json — dil → metin (eksik LanguageKey metinle üretilir)
│                         #   App.SettingDefinitions.json, App.BackgroundWorkers.RecurringJobs.json — ayar tanımları, zamanlanmış işler
├── sql/{object,execute}/ # .sql (SQL Server)
│   └── host/{object,execute}/ # yalnızca host veritabanında çalışır
├── postgres/{object,execute}/
│   └── host/{object,execute}/
└── .imports/{importId}/      # wizard import staging + backup (seeder taramalarına girmez)

Bu dosyalar elle de düzenlenebilir; ilgili ekrandan tekrar kaydedildiğinde yeniden üretilirler. Wizard dosyası EditFileName ile güncellenirse deploy veritabanına dokunmaz: dosyalar cevaptan yeniden yazılır (ad değiştiyse eski dosya silinir), önceki sürümün kayıtları Pending.Replaced'a alınır ve DB Migrate onları kaldırıp ekranı yeniden kurar (lowcode.instructions.md §4.0). Wizard'ın ürettiği ekran ListForm detayından düzenlendiğinde de aynı dosya güncellenir (WizardSeedSynchronizer): ListForm/Fields bölümleri ve Wizard bölümündeki karşılıkları veritabanındaki güncel haliyle değiştirilir.


22. Sorun Giderme

Belirti Olası neden ve çözüm
/api/abp/application-configuration 503 dönüyor Veritabanına erişilemiyor. Bağlantı dizesini ve veri konteynerini kontrol edin; /api/setup/application-status ile durumu görün.
UI /setup ekranına düşüyor Şema oluşmamış veya bekleyen migration var. Setup ekranından migration'ı tetikleyin ya da DbMigrator'ı çalıştırın.
Giriş "UserCannotSignIn..." hatası veriyor 11.3 tablosuna bakın: IP, çalışma saati, doğrulama, şube/eş zamanlı kullanıcı limiti.
Yeni sürüm tarayıcıda görünmüyor Yeni worker indirmeyi bitirince kendiliğinden devreye girer ve sayfa yenilenir (swRegistration.ts). Kurulum takıldıysa sekmeyi tamamen kapatıp açın ya da DevTools › Application › Service Workers'tan eski worker'ı kaldırın.
DevExtreme teması yüklenmiyor public/css altındaki tema dosyaları runtime cache'tedir (StaleWhileRevalidate); hard refresh yeterlidir.
Hangfire başlangıçta hata veriyor Bağlantı dizesi boşsa Hangfire kaydı atlanır ama ABP modülleri yüklenir. Bağlantı dizesini doğrulayın.
Dinamik servis yayınlanmıyor Önce TestCompile çalıştırın; derleme hatası mesajı UI'da döner. Publish için ilgili yetki gerekir.
Raporlarda font/karakter bozukluğu (Linux) DevExpressFontConfiguration.InstallLinuxFonts loglarını kontrol edin; konteynerde font paketi eksik olabilir.

23. Bilinen Sınırlar ve Notlar

  • route.constant.ts içindeki supplychain, maintenance, warehouse, projects, crm, mrp, accounting ve hr yol sabitleri yol sözleşmesidir; bu alanların ekranları fiziksel React sayfaları olarak değil, ListForm konfigürasyonu ile üretilir. hr altında yalnızca organizasyon şeması (OrgChart.tsx, İK menüsü App.Hr.OrgChart) fiziksel bir view'dır.
  • Depodaki appsettings*.json ve deployment dokümanları geliştirme değerleri/örnek kimlik bilgileri içerir; üretim öncesi tümü değiştirilmelidir.
  • SQL View Designer tek yönlüdür (model → T-SQL). Elle yazılmış ya da tasarımcının kanonik şekline uymayan bir view tanımı geri okunamaz; dialog ham SQL moduna düşer.
  • api/test/Sozsoft.Platform.EntityFrameworkCore.Tests şu an SQLite şema oluşturmada (nvarchar(max) → SQLite Error 1) kırıktır; bu kırıklık kod standardından önce de vardı ve ayrı bir iş olarak ele alınmalıdır.
  • Rol–yetki eşleşmeleri veritabanında müşteri ortamına göre değişir; buradaki yetki kodları tanımın kendisidir, atama değil.

Lisans ve Sahiplik

Bu depo Sozsoft'a aittir. DevExpress/DevExtreme bileşenleri ticari lisans gerektirir (api/DevExpress_License.txt).