2026-04-28 14:53:58 +00:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Sozsoft.Platform.Controllers;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// DB hazır olduğunda bile /setup sayfasından migration çalıştırmaya olanak tanır.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Route("api/setup")]
|
|
|
|
|
|
[Authorize(Roles = "admin")]
|
|
|
|
|
|
public class SetupController : ControllerBase
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IConfiguration _configuration;
|
|
|
|
|
|
private readonly IHostEnvironment _env;
|
|
|
|
|
|
|
|
|
|
|
|
public SetupController(
|
|
|
|
|
|
IConfiguration configuration,
|
|
|
|
|
|
IHostEnvironment env)
|
|
|
|
|
|
{
|
|
|
|
|
|
_configuration = configuration;
|
|
|
|
|
|
_env = env;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HttpGet("status")]
|
|
|
|
|
|
[AllowAnonymous]
|
|
|
|
|
|
public IActionResult Status()
|
|
|
|
|
|
{
|
|
|
|
|
|
return Ok(new { dbExists = SetupAppRunner.DatabaseIsReady(_configuration) });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HttpPost("migrate")]
|
|
|
|
|
|
public async Task Migrate(CancellationToken ct)
|
|
|
|
|
|
{
|
|
|
|
|
|
Response.ContentType = "text/event-stream; charset=utf-8";
|
|
|
|
|
|
Response.Headers["Cache-Control"] = "no-cache, no-store";
|
|
|
|
|
|
Response.Headers["X-Accel-Buffering"] = "no";
|
|
|
|
|
|
await Response.Body.FlushAsync(ct);
|
|
|
|
|
|
|
|
|
|
|
|
async Task Send(string level, string message)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var payload = JsonSerializer.Serialize(new { level, message });
|
|
|
|
|
|
await Response.WriteAsync($"data: {payload}\n\n", ct);
|
|
|
|
|
|
await Response.Body.FlushAsync(ct);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch { }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var migratorPath = _configuration["Setup:MigratorPath"]
|
|
|
|
|
|
?? Path.GetFullPath(Path.Combine(_env.ContentRootPath, "..", "Sozsoft.Platform.DbMigrator"));
|
|
|
|
|
|
|
2026-05-03 19:50:51 +00:00
|
|
|
|
await Send("info", "Database migration and seeding are being initiated...");
|
|
|
|
|
|
await Send("info", $"Migrator path: {migratorPath}");
|
2026-04-28 14:53:58 +00:00
|
|
|
|
|
|
|
|
|
|
var extraArgs = _configuration["Setup:MigratorArgs"] ?? "--Seed=true";
|
|
|
|
|
|
|
|
|
|
|
|
string fileName;
|
|
|
|
|
|
string arguments;
|
|
|
|
|
|
string workingDirectory;
|
|
|
|
|
|
|
|
|
|
|
|
if (migratorPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && System.IO.File.Exists(migratorPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
fileName = "dotnet";
|
|
|
|
|
|
arguments = $"\"{migratorPath}\" {extraArgs}";
|
|
|
|
|
|
workingDirectory = Path.GetDirectoryName(migratorPath)!;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Directory.Exists(migratorPath))
|
|
|
|
|
|
{
|
|
|
|
|
|
var dllFiles = Directory.GetFiles(migratorPath, "*.DbMigrator.dll", SearchOption.TopDirectoryOnly);
|
|
|
|
|
|
if (dllFiles.Length == 0)
|
|
|
|
|
|
dllFiles = Directory.GetFiles(migratorPath, "*Migrator*.dll", SearchOption.TopDirectoryOnly);
|
|
|
|
|
|
|
|
|
|
|
|
if (dllFiles.Length > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
fileName = "dotnet";
|
|
|
|
|
|
arguments = $"\"{dllFiles[0]}\" {extraArgs}";
|
|
|
|
|
|
workingDirectory = migratorPath;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
fileName = "dotnet";
|
|
|
|
|
|
arguments = $"run --project \"{migratorPath}\" -- {extraArgs}";
|
|
|
|
|
|
workingDirectory = migratorPath;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
await Send("error", $"Migrator yolu bulunamadı veya geçersiz: {migratorPath}");
|
|
|
|
|
|
await Send("done", "Hata ile sonlandı.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await Send("info", $"Çalıştırılıyor: {fileName} {arguments}");
|
|
|
|
|
|
|
|
|
|
|
|
Process? process = null;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
process = new Process
|
|
|
|
|
|
{
|
|
|
|
|
|
StartInfo = new ProcessStartInfo
|
|
|
|
|
|
{
|
|
|
|
|
|
FileName = fileName,
|
|
|
|
|
|
Arguments = arguments,
|
|
|
|
|
|
RedirectStandardOutput = true,
|
|
|
|
|
|
RedirectStandardError = true,
|
|
|
|
|
|
UseShellExecute = false,
|
|
|
|
|
|
CreateNoWindow = true,
|
|
|
|
|
|
WorkingDirectory = workingDirectory,
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
process.Start();
|
|
|
|
|
|
|
|
|
|
|
|
async Task ReadStream(StreamReader reader, string level)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
while (!reader.EndOfStream)
|
|
|
|
|
|
{
|
|
|
|
|
|
var line = await reader.ReadLineAsync(ct);
|
|
|
|
|
|
if (line != null) await Send(level, line);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException) { }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await Task.WhenAll(
|
|
|
|
|
|
ReadStream(process.StandardOutput, "info"),
|
|
|
|
|
|
ReadStream(process.StandardError, "warn"));
|
|
|
|
|
|
|
|
|
|
|
|
await process.WaitForExitAsync(ct);
|
|
|
|
|
|
|
|
|
|
|
|
if (process.ExitCode == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
await Send("success", "Migration ve seed başarıyla tamamlandı.");
|
|
|
|
|
|
await Send("done", "Tamamlandı.");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
await Send("error", $"Migration başarısız. Çıkış kodu: {process.ExitCode}");
|
|
|
|
|
|
await Send("done", "Hata ile sonlandı.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
await Send("warn", "Migration isteği iptal edildi.");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
await Send("error", $"Migration hatası: {ex.Message}");
|
|
|
|
|
|
await Send("done", "Hata ile sonlandı.");
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
process?.Dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|