diff --git a/README.md b/README.md
index 513339af..9e128a06 100644
--- a/README.md
+++ b/README.md
@@ -225,10 +225,16 @@ 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
+**Veritabanı ilk kurulum:** Açılışta 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).
+ `setupMode: true` ise UI `/setup`'a yönlendirir; katalog ya da çekirdek şema yokken tam yığın da
+ bunu döner (`DatabaseReadiness.RequiresSetup`), bağlantı hatası ise bakım ekranında kalır.
+- Veritabanı tam yığın çalışırken silinirse süreç kurulum moduna geçmez; `DatabaseReadinessGate`
+ kurulum dışı her isteğe 503 döner, `/api/setup/*` isteklerini veritabanına dokunan ara katmanları
+ (dil, tenant, oturum) atlayarak geçirir ve Authorization başlığını düşürür — migration yalnızca
+ kurulum parolasıyla çalışır, bitince uygulama kendini durdurur (process yöneticisi yeniden başlatır).
- 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ı
@@ -2233,7 +2239,7 @@ configs/seeds/
| 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. |
+| `/api/abp/application-configuration` (ve kurulum dışı her uç) 503 dönüyor | Veritabanına erişilemiyor (`DatabaseReadinessGate`). 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](#113-giriş-engelleri) 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. |
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/SetupController.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/SetupController.cs
index b07355b2..5b182221 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/SetupController.cs
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/Controllers/SetupController.cs
@@ -27,8 +27,9 @@ namespace Sozsoft.Platform.Controllers;
/// Canlı sistemde (tam ABP stack ayaktayken) veritabanı durumunu raporlar ve
/// yetkili host kullanıcısının migration tetiklemesine izin verir.
///
-/// Veritabanı hiç yokken bu controller çalışmaz; o durumda
-/// içindeki minimal uygulama devrededir.
+/// Açılışta veritabanı yoksa bu controller çalışmaz; o durumda
+/// içindeki minimal uygulama devrededir. Veritabanı tam stack
+/// çalışırken kaybolursa istekler üzerinden yine buraya ulaşır.
/// İki yol da üzerinden aynı migration akışını kullanır.
///
[Route("api/setup")]
@@ -122,7 +123,11 @@ public class SetupController : ControllerBase
"Database migration triggered via /api/setup/migrate by user {UserId}.",
User?.Identity?.Name ?? "(setup password)");
- await RunMigratorAsync(environment: null, reloadWorkers: true);
+ // Katalog yokken kurulan veritabanı yeni kimliklerle gelir; çalışan stack'in cache'leri,
+ // Hangfire ve OpenIddict depoları eski veritabanına aittir. Minimal kurulum uygulamasındaki
+ // gibi süreç durur, process yöneticisi yeniden başlatır.
+ var restartAfterMigration = DatabaseReadinessChecker.GetStatus(_configuration).RequiresSetup;
+ await RunMigratorAsync(environment: null, reloadWorkers: !restartAfterMigration, restartAfterMigration);
}
///
@@ -170,8 +175,12 @@ public class SetupController : ControllerBase
/// DbMigrator'ü tek kilit altında çalıştırır; migrate ile tenant seed aynı anda yürümez.
/// Worker tanımları host verisidir; tek tenant seed'i onlara dokunmadığı için
/// yalnızca tam migrate'te açıktır.
+ /// açıkken worker tazelenmez, uygulama kendini durdurur.
///
- private async Task RunMigratorAsync(IReadOnlyDictionary? environment, bool reloadWorkers)
+ private async Task RunMigratorAsync(
+ IReadOnlyDictionary? environment,
+ bool reloadWorkers,
+ bool restartAfterMigration = false)
{
var stream = new SetupEventStream(Response);
await stream.StartAsync();
@@ -199,6 +208,18 @@ public class SetupController : ControllerBase
return;
}
+ if (restartAfterMigration)
+ {
+ await stream.SuccessAsync("Migration and seed completed successfully.");
+ await stream.RestartAsync("Application server is restarting...");
+ await stream.DoneAsync("Completed.");
+
+ // İstemcinin "restart" olayını işleyip poll'a geçmesi için kısa gecikme.
+ _ = Task.Delay(TimeSpan.FromSeconds(1.5))
+ .ContinueWith(_ => _lifetime.StopApplication(), TaskScheduler.Default);
+ return;
+ }
+
if (!reloadWorkers)
{
await stream.SuccessAsync("Migration and seed completed successfully.");
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/DatabaseReadiness.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/DatabaseReadiness.cs
index 98d0be98..ffff7bc5 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/DatabaseReadiness.cs
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/DatabaseReadiness.cs
@@ -19,6 +19,13 @@ public sealed record DatabaseReadiness(
/// Uygulamanın tam ABP stack ile açılabilmesi için yeterli mi.
public bool IsReady => DatabaseExists && SchemaReady;
+ ///
+ /// Kurulum gerekiyor mu: sunucuya ulaşıldı ama katalog ya da çekirdek şema yok. Bağlantı/oturum
+ /// hatası ( dolu) geçici sayılır ve kurulum değildir; o durumda istemci
+ /// bakım ekranında kalır.
+ ///
+ public bool RequiresSetup => !IsReady && Error is null;
+
public static DatabaseReadiness NotReady(string? error)
=> new(false, false, null, error);
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/DatabaseReadinessGate.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/DatabaseReadinessGate.cs
new file mode 100644
index 00000000..02287c8a
--- /dev/null
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/DatabaseReadinessGate.cs
@@ -0,0 +1,66 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Net.Http.Headers;
+
+namespace Sozsoft.Platform.DbStartup;
+
+///
+/// Veritabanı hazır değilken boru hattının kapısı.
+///
+/// Kurulum moduna yalnızca açılışta karar verilir (); tam ABP stack
+/// çalışırken veritabanı kaybolursa (katalog düşürüldü, sunucu erişilemez) süreç kurulum moduna
+/// geçmez. Bu durumda istek yolundaki dil sağlayıcısı, tenant çözümleyici ve oturum doğrulaması
+/// veritabanına gidip 500 ile düşüyordu — kurulumu yapacak /api/setup/* uçları da dahil.
+///
+/// - Kurulum dışı her isteğe anında 503 döner; EF sorguları ve bağlantı zaman aşımı devreye girmez.
+/// - /api/setup/* isteklerini geçirir ama veritabanına dokunan ara katmanların atlanması için
+/// işaretler (). Oturum veritabanı olmadan doğrulanamayacağı için
+/// Authorization başlığı düşürülür; migration bu durumda yalnızca kurulum parolasıyla çalışır
+/// ().
+///
+/// Kontrol cache'lidir (), istek başına maliyeti yoktur.
+///
+public sealed class DatabaseReadinessGate
+{
+ public const string SetupPathPrefix = "/api/setup";
+
+ private const string BypassItemKey = "Sozsoft.DatabaseReadinessGate.Bypass";
+
+ private readonly RequestDelegate _next;
+ private readonly IConfiguration _configuration;
+
+ public DatabaseReadinessGate(RequestDelegate next, IConfiguration configuration)
+ {
+ _next = next;
+ _configuration = configuration;
+ }
+
+ public async Task InvokeAsync(HttpContext context)
+ {
+ if (DatabaseReadinessChecker.GetStatus(_configuration).IsReady)
+ {
+ await _next(context);
+ return;
+ }
+
+ if (context.Request.Path.StartsWithSegments(SetupPathPrefix))
+ {
+ context.Items[BypassItemKey] = true;
+ context.Request.Headers.Remove(HeaderNames.Authorization);
+ await _next(context);
+ return;
+ }
+
+ context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
+ context.Response.ContentType = "text/plain; charset=utf-8";
+ await context.Response.WriteAsync("The database is not reachable.");
+ }
+
+ ///
+ /// Bu istekte veritabanına dokunan ara katmanlar atlanmalı mı (veritabanı hazır değilken
+ /// geçirilen kurulum isteği).
+ ///
+ public static bool IsDatabaseBypassed(HttpContext context)
+ => context.Items.ContainsKey(BypassItemKey);
+}
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupStatusDto.cs b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupStatusDto.cs
index 4c4135bf..5a2e58fe 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupStatusDto.cs
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/DbStartup/SetupStatusDto.cs
@@ -12,11 +12,12 @@ public sealed class SetupStatusDto
public bool DbExists { get; init; }
///
- /// Sunucu kurulum modunda mı (minimal uygulama) çalışıyor.
+ /// Kurulum ekranı gösterilmeli mi. Frontend yalnızca bu değer true iken /setup
+ /// sayfasına yönlendirir.
///
- /// Frontend yalnızca bu değer true iken /setup sayfasına yönlendirir. Tam ABP stack
- /// ayaktayken bu değer her zaman false'tur; böylece canlı sistemde geçici bir
- /// veritabanı sorunu anonim kullanıcıyı kurulum ekranına düşürmez.
+ /// Minimal kurulum uygulamasında her zaman true. Tam ABP stack ayaktayken yalnızca
+ /// katalog/şema yokken () true; bağlantı
+ /// hatası gibi geçici bir sorun anonim kullanıcıyı kurulum ekranına düşürmez.
///
public bool SetupMode { get; init; }
@@ -37,7 +38,7 @@ public sealed class SetupStatusDto
return new SetupStatusDto
{
DbExists = readiness.IsReady,
- SetupMode = setupMode,
+ SetupMode = setupMode || readiness.RequiresSetup,
PendingMigrationCount = includeDiagnostics ? readiness.PendingMigrationCount : null,
// Hata detayı bağlantı dizesi/sunucu adı sızdırabilir — sadece yetkiliye.
Error = includeDiagnostics ? readiness.Error : null,
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSessionValidationMiddleware.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSessionValidationMiddleware.cs
index 8d348aa1..36bc1158 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSessionValidationMiddleware.cs
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSessionValidationMiddleware.cs
@@ -15,8 +15,7 @@ namespace Sozsoft.Platform.Identity;
public class PlatformSessionValidationMiddleware : IMiddleware, ITransientDependency
{
- private const string SetupPathPrefix = "/api/setup";
- private const string SetupStatusPath = "/api/setup/application-status";
+ private const string SetupStatusPath = DatabaseReadinessGate.SetupPathPrefix + "/application-status";
private readonly IIdentitySessionRepository sessionRepository;
private readonly ICurrentTenant currentTenant;
@@ -101,7 +100,7 @@ public class PlatformSessionValidationMiddleware : IMiddleware, ITransientDepend
{
var path = context.Request.Path;
- if (!path.StartsWithSegments(SetupPathPrefix))
+ if (!path.StartsWithSegments(DatabaseReadinessGate.SetupPathPrefix))
{
return false;
}
diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs
index 9ba1c014..67e0de2a 100644
--- a/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs
+++ b/api/src/Sozsoft.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs
@@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Extensions.DependencyInjection;
-using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.ResponseCompression;
@@ -471,7 +470,12 @@ public class PlatformHttpApiHostModule : AbpModule
app.UseDeveloperExceptionPage();
}
- app.UseAbpRequestLocalization();
+ // Veritabanı hazır değilken kurulum dışı istekler burada 503 ile kesilir; kurulum istekleri
+ // veritabanına dokunan ara katmanları (dil, tenant) atlayarak SetupController'a ulaşır.
+ app.UseMiddleware();
+ app.UseWhen(
+ httpContext => !DatabaseReadinessGate.IsDatabaseBypassed(httpContext),
+ branch => branch.UseAbpRequestLocalization());
// Auth hataları MVC tarafından /Error sayfasına yönlendirilmesin diye bu kaldırıldı
// if (!env.IsDevelopment())
@@ -483,29 +487,6 @@ public class PlatformHttpApiHostModule : AbpModule
app.UseResponseCompression();
app.MapAbpStaticAssets();
app.UseRouting();
- // Not: /api/setup/application-status artık SetupController tarafından karşılanıyor.
- // Buradaki kopya middleware kaldırıldı — durum mantığı tek yerde (DatabaseReadinessChecker).
-
- // Veritabanı erişilemezken uygulama konfigürasyonu üretilemez. İsteği burada,
- // EF sorguları ve bağlantı zaman aşımları devreye girmeden kesiyoruz: aksi halde
- // istemci 10+ saniye bekleyip, contributor'ın içinde patlayan bir bağlantı
- // hatasından türeyen belirsiz bir yanıt alıyordu. Kontrol cache'li, maliyeti yok.
- app.Use(async (httpContext, next) =>
- {
- if (httpContext.Request.Path.StartsWithSegments("/api/abp/application-configuration"))
- {
- var configuration = httpContext.RequestServices.GetRequiredService();
- if (!DatabaseReadinessChecker.GetStatus(configuration).IsReady)
- {
- httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
- httpContext.Response.ContentType = "text/plain; charset=utf-8";
- await httpContext.Response.WriteAsync("The database is not reachable.");
- return;
- }
- }
-
- await next();
- });
app.Use(async (context, next) =>
{
@@ -528,8 +509,13 @@ public class PlatformHttpApiHostModule : AbpModule
if (PlatformConsts.IsMultiTenant)
{
- app.UseMultiTenancy();
- app.UseMiddleware();
+ app.UseWhen(
+ httpContext => !DatabaseReadinessGate.IsDatabaseBypassed(httpContext),
+ branch =>
+ {
+ branch.UseMultiTenancy();
+ branch.UseMiddleware();
+ });
}
app.UseUnitOfWork();