generated from fahricansecer/boilerplate-be
141 lines
6.1 KiB
C#
141 lines
6.1 KiB
C#
using Microsoft.Extensions.Configuration;
|
||
using Microsoft.Extensions.Logging;
|
||
using Npgsql;
|
||
|
||
namespace SaasMediaWorker.Services;
|
||
|
||
/// <summary>
|
||
/// PostgreSQL veritabanı servisi — RenderJob ve Project durumlarını günceller.
|
||
/// NestJS Prisma schema'sıyla uyumlu SQL sorguları kullanır.
|
||
///
|
||
/// Neden doğrudan SQL (ORM yerine)?
|
||
/// - C# Worker minimum footprint olmalı (16GB RPi).
|
||
/// - Sadece UPDATE sorguları yapılıyor — ORM gereksiz overhead.
|
||
/// - Npgsql ARM64'te native çalışır.
|
||
/// </summary>
|
||
public class DatabaseService
|
||
{
|
||
private readonly ILogger<DatabaseService> _logger;
|
||
private readonly string _connectionString;
|
||
|
||
public DatabaseService(
|
||
ILogger<DatabaseService> logger,
|
||
IConfiguration configuration)
|
||
{
|
||
_logger = logger;
|
||
_connectionString = configuration.GetConnectionString("DefaultConnection")
|
||
?? throw new InvalidOperationException("DefaultConnection bağlantı dizesi bulunamadı");
|
||
}
|
||
|
||
/// <summary>
|
||
/// RenderJob tablosunun durumunu günceller.
|
||
/// </summary>
|
||
public async Task UpdateRenderJobStatus(
|
||
string renderJobId,
|
||
string status,
|
||
int progress,
|
||
string? currentStage,
|
||
string? errorMessage = null,
|
||
string? errorStack = null,
|
||
long? processingTimeMs = null,
|
||
string? workerVersion = null,
|
||
string? workerHostname = null)
|
||
{
|
||
await using var conn = new NpgsqlConnection(_connectionString);
|
||
await conn.OpenAsync();
|
||
|
||
var sql = @"
|
||
UPDATE ""RenderJob""
|
||
SET ""status"" = @status::""RenderJobStatus"",
|
||
""progress"" = @progress,
|
||
""currentStage"" = CASE WHEN @stage IS NOT NULL THEN @stage::""RenderStage"" ELSE ""currentStage"" END,
|
||
""errorMessage"" = COALESCE(@errorMessage, ""errorMessage""),
|
||
""errorStack"" = COALESCE(@errorStack, ""errorStack""),
|
||
""processingTimeMs"" = COALESCE(@processingTimeMs, ""processingTimeMs""),
|
||
""workerVersion"" = COALESCE(@workerVersion, ""workerVersion""),
|
||
""workerHostname"" = COALESCE(@workerHostname, ""workerHostname""),
|
||
""startedAt"" = CASE WHEN @status = 'PROCESSING' AND ""startedAt"" IS NULL THEN NOW() ELSE ""startedAt"" END,
|
||
""completedAt"" = CASE WHEN @status IN ('COMPLETED', 'FAILED') THEN NOW() ELSE ""completedAt"" END,
|
||
""lastErrorAt"" = CASE WHEN @status = 'FAILED' THEN NOW() ELSE ""lastErrorAt"" END,
|
||
""updatedAt"" = NOW()
|
||
WHERE ""id"" = @id";
|
||
|
||
await using var cmd = new NpgsqlCommand(sql, conn);
|
||
cmd.Parameters.AddWithValue("id", renderJobId);
|
||
cmd.Parameters.AddWithValue("status", status);
|
||
cmd.Parameters.AddWithValue("progress", progress);
|
||
cmd.Parameters.AddWithValue("stage", (object?)currentStage ?? DBNull.Value);
|
||
cmd.Parameters.AddWithValue("errorMessage", (object?)errorMessage ?? DBNull.Value);
|
||
cmd.Parameters.AddWithValue("errorStack", (object?)errorStack ?? DBNull.Value);
|
||
cmd.Parameters.AddWithValue("processingTimeMs", (object?)processingTimeMs ?? DBNull.Value);
|
||
cmd.Parameters.AddWithValue("workerVersion", (object?)workerVersion ?? DBNull.Value);
|
||
cmd.Parameters.AddWithValue("workerHostname", (object?)workerHostname ?? DBNull.Value);
|
||
|
||
var affected = await cmd.ExecuteNonQueryAsync();
|
||
_logger.LogDebug("RenderJob güncellendi: {Id} → {Status} ({Progress}%)", renderJobId, status, progress);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Project tablosunun durumunu günceller.
|
||
/// </summary>
|
||
public async Task UpdateProjectStatus(
|
||
string projectId,
|
||
string status,
|
||
int progress,
|
||
string? finalVideoUrl = null,
|
||
string? errorMessage = null)
|
||
{
|
||
await using var conn = new NpgsqlConnection(_connectionString);
|
||
await conn.OpenAsync();
|
||
|
||
var sql = @"
|
||
UPDATE ""Project""
|
||
SET ""status"" = @status::""ProjectStatus"",
|
||
""progress"" = @progress,
|
||
""finalVideoUrl"" = COALESCE(@finalVideoUrl, ""finalVideoUrl""),
|
||
""errorMessage"" = @errorMessage,
|
||
""completedAt"" = CASE WHEN @status = 'COMPLETED' THEN NOW() ELSE ""completedAt"" END,
|
||
""updatedAt"" = NOW()
|
||
WHERE ""id"" = @id";
|
||
|
||
await using var cmd = new NpgsqlCommand(sql, conn);
|
||
cmd.Parameters.AddWithValue("id", projectId);
|
||
cmd.Parameters.AddWithValue("status", status);
|
||
cmd.Parameters.AddWithValue("progress", progress);
|
||
cmd.Parameters.AddWithValue("finalVideoUrl", (object?)finalVideoUrl ?? DBNull.Value);
|
||
cmd.Parameters.AddWithValue("errorMessage", (object?)errorMessage ?? DBNull.Value);
|
||
|
||
await cmd.ExecuteNonQueryAsync();
|
||
_logger.LogDebug("Project güncellendi: {Id} → {Status} ({Progress}%)", projectId, status, progress);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Render log kaydı ekler.
|
||
/// </summary>
|
||
public async Task AddRenderLog(
|
||
string renderJobId,
|
||
string stage,
|
||
string message,
|
||
string level = "info",
|
||
int? durationMs = null,
|
||
string? metadata = null)
|
||
{
|
||
await using var conn = new NpgsqlConnection(_connectionString);
|
||
await conn.OpenAsync();
|
||
|
||
var sql = @"
|
||
INSERT INTO ""RenderLog"" (""id"", ""renderJobId"", ""stage"", ""message"", ""level"", ""durationMs"", ""metadata"", ""createdAt"")
|
||
VALUES (gen_random_uuid(), @renderJobId, @stage::""RenderStage"", @message, @level, @durationMs, @metadata::jsonb, NOW())";
|
||
|
||
await using var cmd = new NpgsqlCommand(sql, conn);
|
||
cmd.Parameters.AddWithValue("renderJobId", renderJobId);
|
||
cmd.Parameters.AddWithValue("stage", stage);
|
||
cmd.Parameters.AddWithValue("message", message);
|
||
cmd.Parameters.AddWithValue("level", level);
|
||
cmd.Parameters.AddWithValue("durationMs", (object?)durationMs ?? DBNull.Value);
|
||
cmd.Parameters.AddWithValue("metadata", (object?)metadata ?? DBNull.Value);
|
||
|
||
await cmd.ExecuteNonQueryAsync();
|
||
}
|
||
}
|