diff --git a/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs b/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs
new file mode 100644
index 00000000..b950e70d
--- /dev/null
+++ b/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+using Volo.Abp.Application.Dtos;
+
+namespace Kurs.Platform.DeveloperKit;
+
+///
+/// Dynamic AppService tanımı DTO'su
+///
+public class DynamicServiceDto : FullAuditedEntityDto
+{
+ [StringLength(256)]
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [StringLength(512)]
+ [JsonPropertyName("displayName")]
+ public string DisplayName { get; set; }
+
+ [StringLength(2000)]
+ [JsonPropertyName("description")]
+ public string Description { get; set; }
+
+ [JsonPropertyName("code")]
+ public string Code { get; set; }
+
+ [JsonPropertyName("isActive")]
+ public bool IsActive { get; set; }
+
+ [JsonPropertyName("compilationStatus")]
+ public string CompilationStatus { get; set; }
+
+ [JsonPropertyName("lastCompilationError")]
+ public string LastCompilationError { get; set; }
+
+ [JsonPropertyName("lastSuccessfulCompilation")]
+ public DateTime? LastSuccessfulCompilation { get; set; }
+
+ [JsonPropertyName("version")]
+ public int Version { get; set; }
+
+ [JsonPropertyName("codeHash")]
+ public string CodeHash { get; set; }
+
+ [JsonPropertyName("primaryEntityType")]
+ public string PrimaryEntityType { get; set; }
+
+ [JsonPropertyName("controllerName")]
+ public string ControllerName { get; set; }
+}
+
+///
+/// Kod derleme sonucu DTO'su
+///
+public class CompileResultDto
+{
+ [JsonPropertyName("success")]
+ public bool Success { get; set; }
+ [JsonPropertyName("errorMessage")]
+ public string ErrorMessage { get; set; }
+ [JsonPropertyName("errors")]
+ public List Errors { get; set; }
+ [JsonPropertyName("compilationTimeMs")]
+ public long CompilationTimeMs { get; set; }
+ [JsonPropertyName("hasWarnings")]
+ public bool HasWarnings { get; set; }
+ [JsonPropertyName("warnings")]
+ public List Warnings { get; set; }
+}
+
+///
+/// Tek bir derleme hatası
+///
+public class CompilationErrorDto
+{
+ [JsonPropertyName("code")]
+ public string Code { get; set; }
+ [JsonPropertyName("message")]
+ public string Message { get; set; }
+ [JsonPropertyName("line")]
+ public int Line { get; set; }
+ [JsonPropertyName("column")]
+ public int Column { get; set; }
+ [JsonPropertyName("severity")]
+ public string Severity { get; set; }
+ [JsonPropertyName("fileName")]
+ public string FileName { get; set; }
+}
+
+///
+/// Kod test etme DTO'su
+///
+public class TestCompileRequestDto
+{
+ [JsonPropertyName("code")]
+ public string Code { get; set; }
+}
+
+///
+/// AppService yayınlama DTO'su
+///
+public class PublishAppServiceRequestDto
+{
+ [StringLength(256)]
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("code")]
+ public string Code { get; set; }
+
+ [StringLength(512)]
+ [JsonPropertyName("displayName")]
+ public string DisplayName { get; set; }
+
+ [StringLength(2000)]
+ [JsonPropertyName("description")]
+ public string Description { get; set; }
+
+ [StringLength(256)]
+ [JsonPropertyName("primaryEntityType")]
+ public string PrimaryEntityType { get; set; }
+}
+
+///
+/// AppService yayınlama sonucu
+///
+public class PublishResultDto
+{
+ [JsonPropertyName("success")]
+ public bool Success { get; set; }
+ [JsonPropertyName("errorMessage")]
+ public string ErrorMessage { get; set; }
+ [JsonPropertyName("appServiceId")]
+ public Guid? AppServiceId { get; set; }
+ [JsonPropertyName("swaggerUrl")]
+ public string SwaggerUrl { get; set; }
+ [JsonPropertyName("generatedEndpoints")]
+ public List GeneratedEndpoints { get; set; }
+ [JsonPropertyName("controllerName")]
+ public string ControllerName { get; set; }
+}
diff --git a/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/IDynamicServiceManager.cs b/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/IDynamicServiceManager.cs
new file mode 100644
index 00000000..2ddd63ac
--- /dev/null
+++ b/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/IDynamicServiceManager.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+
+namespace Kurs.Platform.DeveloperKit;
+
+///
+/// Dynamic AppService yönetimi için application service interface
+///
+public interface IDynamicServiceManager : IApplicationService
+{
+ ///
+ /// C# kodunu test compile eder (herhangi bir assembly yüklemez)
+ ///
+ /// Test edilecek kod
+ /// Derleme sonucu
+ Task TestCompileAsync(TestCompileRequestDto request);
+
+ ///
+ /// AppService'i yayınlar (compile eder, veritabanına kaydeder ve runtime'da yükler)
+ ///
+ /// Yayınlanacak AppService bilgileri
+ /// Yayınlama sonucu
+ Task PublishAsync(PublishAppServiceRequestDto request);
+
+ ///
+ /// Mevcut tenant'ın AppService tanımlarını listeler
+ ///
+ /// Sayfalama parametreleri
+ /// AppService listesi
+ Task> GetListAsync(PagedAndSortedResultRequestDto input);
+
+ ///
+ /// Belirli bir AppService tanımını getirir
+ ///
+ /// AppService ID
+ /// AppService tanımı
+ Task GetAsync(Guid id);
+
+ ///
+ /// AppService tanımını siler ve runtime'dan kaldırır
+ ///
+ /// AppService ID
+ Task DeleteAsync(Guid id);
+
+ ///
+ /// AppService'i aktif/pasif yapar
+ ///
+ /// AppService ID
+ /// Aktif durumu
+ Task SetActiveAsync(Guid id, bool isActive);
+
+ ///
+ /// Tüm aktif AppService'leri yeniden yükler (uygulama restart'tan sonra)
+ ///
+ Task ReloadAllActiveServicesAsync();
+
+ ///
+ /// Belirli bir AppService'i yeniden derler ve yükler
+ ///
+ /// AppService ID
+ /// Yeniden derleme sonucu
+ Task RecompileAsync(Guid id);
+}
diff --git a/api/src/Kurs.Platform.Application/DeveloperKit/DeveloperKitAutoMapperProfile.cs b/api/src/Kurs.Platform.Application/DeveloperKit/DeveloperKitAutoMapperProfile.cs
index dc1dfe61..9ccb3667 100644
--- a/api/src/Kurs.Platform.Application/DeveloperKit/DeveloperKitAutoMapperProfile.cs
+++ b/api/src/Kurs.Platform.Application/DeveloperKit/DeveloperKitAutoMapperProfile.cs
@@ -27,5 +27,8 @@ public class DeveloperKitAutoMapperProfile : Profile
// Migration mappings
CreateMap();
CreateMap();
+
+ CreateMap()
+ .ForMember(dest => dest.CompilationStatus, opt => opt.MapFrom(src => src.CompilationStatus.ToString()));
}
}
diff --git a/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs b/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs
new file mode 100644
index 00000000..aa99d0cf
--- /dev/null
+++ b/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs
@@ -0,0 +1,399 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.Extensions.Logging;
+using Volo.Abp.DependencyInjection;
+using System.Diagnostics;
+using System.Collections.Concurrent;
+using Kurs.Platform.DeveloperKit;
+
+namespace Kurs.Platform.DynamicServices;
+
+///
+/// Dynamic C# kod derleme servisi
+///
+public class DynamicServiceCompiler : ITransientDependency
+{
+ private readonly ILogger _logger;
+
+ // Tenant bazlı yüklenmiş assembly'leri takip etmek için
+ private static readonly ConcurrentDictionary> _tenantAssemblies = new();
+
+ // Assembly kaydı için delegate
+ public static Action? NotifyAssemblyRegistration { get; set; }
+
+ // Güvenlik için yasaklı namespace'ler
+ private static readonly string[] ForbiddenNamespaces = {
+ "System.IO",
+ "System.Diagnostics",
+ "System.Threading.Thread",
+ "System.Environment",
+ "System.Net.Sockets",
+ "System.Reflection.Emit",
+ "System.Runtime.InteropServices",
+ "Microsoft.Win32",
+ "System.Security.Cryptography",
+ "System.Net.NetworkInformation"
+ };
+
+ // Güvenlik için yasaklı sınıflar/metotlar
+ private static readonly string[] ForbiddenTypes = {
+ "Process",
+ "ProcessStartInfo",
+ "Thread",
+ "ThreadStart",
+ "File",
+ "Directory",
+ "FileStream",
+ "StreamWriter",
+ "StreamReader",
+ "Socket",
+ "TcpClient",
+ "UdpClient",
+ "HttpWebRequest",
+ "WebClient",
+ "Assembly.Load"
+ };
+
+ public DynamicServiceCompiler(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// Kodu derler ve validate eder, ancak assembly yüklemez
+ ///
+ public async Task CompileAndValidateAsync(string code, Guid? tenantId = null)
+ {
+ var stopwatch = Stopwatch.StartNew();
+
+ try
+ {
+ // Güvenlik kontrolü
+ var securityCheck = ValidateCodeSecurity(code);
+ if (!securityCheck.Success)
+ {
+ return securityCheck;
+ }
+
+ // Roslyn ile derleme
+ var compilation = CreateCompilation(code, $"DynamicAssembly_{Guid.NewGuid()}");
+
+ using var ms = new MemoryStream();
+ var emitResult = compilation.Emit(ms);
+
+ stopwatch.Stop();
+
+ var result = new CompileResultDto
+ {
+ Success = emitResult.Success,
+ CompilationTimeMs = stopwatch.ElapsedMilliseconds,
+ Errors = new List(),
+ Warnings = new List()
+ };
+
+ // Hataları ve uyarıları topla
+ foreach (var diagnostic in emitResult.Diagnostics)
+ {
+ var error = new CompilationErrorDto
+ {
+ Code = diagnostic.Id,
+ Message = diagnostic.GetMessage(),
+ Severity = diagnostic.Severity.ToString(),
+ Line = diagnostic.Location.GetLineSpan().StartLinePosition.Line + 1,
+ Column = diagnostic.Location.GetLineSpan().StartLinePosition.Character + 1,
+ FileName = diagnostic.Location.SourceTree?.FilePath ?? "DynamicCode.cs"
+ };
+
+ if (diagnostic.Severity == DiagnosticSeverity.Error)
+ {
+ result.Errors.Add(error);
+ }
+ else if (diagnostic.Severity == DiagnosticSeverity.Warning)
+ {
+ result.Warnings.Add(error.Message);
+ result.HasWarnings = true;
+ }
+ }
+
+ if (!result.Success)
+ {
+ result.ErrorMessage = $"Derleme {result.Errors.Count} hata ile başarısız oldu.";
+ }
+
+ _logger.LogInformation("Kod derlemesi tamamlandı. Başarılı: {Success}, Süre: {Time}ms, Hata sayısı: {ErrorCount}",
+ result.Success, stopwatch.ElapsedMilliseconds, result.Errors?.Count ?? 0);
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ stopwatch.Stop();
+ _logger.LogError(ex, "Kod derlemesi sırasında beklenmeyen hata");
+
+ return new CompileResultDto
+ {
+ Success = false,
+ ErrorMessage = $"Derleme sırasında hata: {ex.Message}",
+ CompilationTimeMs = stopwatch.ElapsedMilliseconds,
+ Errors = new List()
+ };
+ }
+ }
+
+ ///
+ /// Kodu derler ve belirtilen tenant için assembly yükler
+ ///
+ public async Task CompileAndRegisterForTenantAsync(Guid tenantId, string code, string assemblyName)
+ {
+ try
+ {
+ // Önce validate et
+ var validateResult = await CompileAndValidateAsync(code, tenantId);
+ if (!validateResult.Success)
+ {
+ return validateResult;
+ }
+
+ // Assembly oluştur
+ var compilation = CreateCompilation(code, assemblyName);
+
+ using var ms = new MemoryStream();
+ var emitResult = compilation.Emit(ms);
+
+ if (!emitResult.Success)
+ {
+ return validateResult; // Zaten hata bilgisi mevcut
+ }
+
+ ms.Seek(0, SeekOrigin.Begin);
+
+ // Tenant'a özel assembly load context
+ var contextName = $"Tenant_{tenantId}_Context";
+ var loadContext = new AssemblyLoadContext(contextName, isCollectible: true);
+
+ var assembly = loadContext.LoadFromStream(ms);
+
+ // ApplicationService türevlerini bul
+ var appServiceTypes = assembly.GetTypes()
+ .Where(t => IsApplicationServiceType(t))
+ .ToList();
+
+ _logger.LogInformation("Tenant {TenantId} için {Count} ApplicationService bulundu",
+ tenantId, appServiceTypes.Count);
+
+ // Tenant assembly'leri listesine ekle
+ _tenantAssemblies.AddOrUpdate(tenantId,
+ new List { assembly },
+ (key, existing) => { existing.Add(assembly); return existing; });
+
+ // Background service'e assembly kaydı isteği gönder
+ // Bu, DI container ve MVC conventional controller sistemine kaydetmek için
+ NotifyAssemblyRegistration?.Invoke(tenantId, assembly, assemblyName);
+
+ _logger.LogInformation("Tenant {TenantId} için assembly başarıyla yüklendi: {AssemblyName}",
+ tenantId, assemblyName);
+
+ return new CompileResultDto
+ {
+ Success = true,
+ CompilationTimeMs = validateResult.CompilationTimeMs
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Assembly yükleme sırasında hata. Tenant: {TenantId}", tenantId);
+
+ return new CompileResultDto
+ {
+ Success = false,
+ ErrorMessage = $"Assembly yükleme hatası: {ex.Message}",
+ Errors = new List()
+ };
+ }
+ }
+
+ ///
+ /// Kod güvenlik kontrolü
+ ///
+ private CompileResultDto ValidateCodeSecurity(string code)
+ {
+ var errors = new List();
+
+ // Yasaklı namespace kontrolü
+ foreach (var forbiddenNs in ForbiddenNamespaces)
+ {
+ if (code.Contains($"using {forbiddenNs}") || code.Contains($"{forbiddenNs}."))
+ {
+ errors.Add(new CompilationErrorDto
+ {
+ Code = "SECURITY001",
+ Message = $"Güvenlik nedeniyle '{forbiddenNs}' namespace'i kullanılamaz",
+ Severity = "Error",
+ Line = GetLineNumber(code, forbiddenNs),
+ Column = 1
+ });
+ }
+ }
+
+ // Yasaklı tip kontrolü
+ foreach (var forbiddenType in ForbiddenTypes)
+ {
+ if (code.Contains(forbiddenType))
+ {
+ errors.Add(new CompilationErrorDto
+ {
+ Code = "SECURITY002",
+ Message = $"Güvenlik nedeniyle '{forbiddenType}' tipi kullanılamaz",
+ Severity = "Error",
+ Line = GetLineNumber(code, forbiddenType),
+ Column = 1
+ });
+ }
+ }
+
+ return new CompileResultDto
+ {
+ Success = errors.Count == 0,
+ ErrorMessage = errors.Count > 0 ? "Güvenlik kontrolü başarısız" : null,
+ Errors = errors
+ };
+ }
+
+ ///
+ /// Roslyn compilation oluşturur
+ ///
+ private CSharpCompilation CreateCompilation(string code, string assemblyName)
+ {
+ var syntaxTree = CSharpSyntaxTree.ParseText(code);
+
+ var references = GetDefaultReferences();
+
+ return CSharpCompilation.Create(
+ assemblyName,
+ new[] { syntaxTree },
+ references,
+ new CSharpCompilationOptions(
+ OutputKind.DynamicallyLinkedLibrary,
+ optimizationLevel: OptimizationLevel.Debug,
+ allowUnsafe: false // Güvenlik için unsafe kod yasağı
+ ));
+ }
+
+ ///
+ /// Varsayılan assembly referanslarını döner
+ ///
+ private List GetDefaultReferences()
+ {
+ var references = new List();
+
+ // .NET Core temel referanslar
+ var runtimeAssemblies = new[]
+ {
+ typeof(object).Assembly, // System.Private.CoreLib
+ typeof(Console).Assembly, // System.Console
+ typeof(System.ComponentModel.DataAnnotations.RequiredAttribute).Assembly, // System.ComponentModel.DataAnnotations
+ typeof(System.Linq.Enumerable).Assembly, // System.Linq
+ typeof(System.Collections.Generic.List<>).Assembly, // System.Collections
+ Assembly.Load("System.Runtime"),
+ Assembly.Load("System.Collections"),
+ Assembly.Load("netstandard")
+ };
+
+ foreach (var assembly in runtimeAssemblies)
+ {
+ try
+ {
+ references.Add(MetadataReference.CreateFromFile(assembly.Location));
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Assembly referansı eklenemedi: {Assembly}", assembly.FullName);
+ }
+ }
+
+ // ABP Framework referansları
+ try
+ {
+ var abpAssemblies = AppDomain.CurrentDomain.GetAssemblies()
+ .Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location))
+ .Where(a => !string.IsNullOrEmpty(a.FullName) &&
+ (a.FullName.Contains("Volo.Abp") ||
+ a.FullName.Contains("Kurs.Platform") ||
+ a.FullName.Contains("Microsoft.AspNetCore")))
+ .ToList();
+
+ foreach (var assembly in abpAssemblies)
+ {
+ references.Add(MetadataReference.CreateFromFile(assembly.Location));
+ }
+
+ _logger.LogDebug("Toplam {Count} referans eklendi", references.Count);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "ABP referansları eklenirken hata");
+ }
+
+ return references;
+ }
+
+ ///
+ /// Bir tipin ApplicationService olup olmadığını kontrol eder
+ ///
+ private bool IsApplicationServiceType(Type type)
+ {
+ try
+ {
+ return !type.IsAbstract &&
+ !type.IsInterface &&
+ type.IsClass &&
+ (type.Name.EndsWith("AppService") || type.Name.EndsWith("ApplicationService")) &&
+ HasApplicationServiceBase(type);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Tip kontrolü sırasında hata: {Type}", type?.FullName);
+ return false;
+ }
+ }
+
+ ///
+ /// Tipin ApplicationService base class'ından türediğini kontrol eder
+ ///
+ private bool HasApplicationServiceBase(Type type)
+ {
+ var currentType = type.BaseType;
+ while (currentType != null)
+ {
+ if (currentType.Name.Contains("ApplicationService"))
+ {
+ return true;
+ }
+ currentType = currentType.BaseType;
+ }
+ return false;
+ }
+
+ ///
+ /// Kod içinde belirli bir string'in satır numarasını bulur
+ ///
+ private int GetLineNumber(string code, string searchText)
+ {
+ var lines = code.Split('\n');
+ for (int i = 0; i < lines.Length; i++)
+ {
+ if (lines[i].Contains(searchText))
+ {
+ return i + 1;
+ }
+ }
+ return 1;
+ }
+}
diff --git a/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceManager.cs b/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceManager.cs
new file mode 100644
index 00000000..76f1ef4e
--- /dev/null
+++ b/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceManager.cs
@@ -0,0 +1,375 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+using Volo.Abp.Domain.Repositories;
+using Volo.Abp.MultiTenancy;
+using Microsoft.AspNetCore.Authorization;
+using Kurs.Platform.Entities;
+using Kurs.Platform.DeveloperKit;
+
+namespace Kurs.Platform.DynamicServices;
+
+///
+/// Dynamic AppService yönetimi için ApplicationService implementasyonu
+///
+[Authorize] // Sadece giriş yapmış kullanıcılar erişebilir
+public class DynamicServiceManager : ApplicationService, IDynamicServiceManager
+{
+ private readonly IRepository _dynamicAppServiceRepository;
+ private readonly DynamicServiceCompiler _compiler;
+ private readonly ICurrentTenant _currentTenant;
+
+ public DynamicServiceManager(
+ IRepository dynamicAppServiceRepository,
+ DynamicServiceCompiler compiler,
+ ICurrentTenant currentTenant)
+ {
+ _dynamicAppServiceRepository = dynamicAppServiceRepository;
+ _compiler = compiler;
+ _currentTenant = currentTenant;
+ }
+
+ ///
+ /// C# kodunu test compile eder
+ ///
+ [Authorize(PlatformConsts.DynamicServices.TestCompile)]
+ public async Task TestCompileAsync(TestCompileRequestDto request)
+ {
+ Logger.LogInformation("Test compile başlatıldı. Tenant: {TenantId}", _currentTenant.Id);
+
+ try
+ {
+ var result = await _compiler.CompileAndValidateAsync(request.Code, _currentTenant.Id);
+
+ Logger.LogInformation("Test compile tamamlandı. Başarılı: {Success}, Tenant: {TenantId}",
+ result.Success, _currentTenant.Id);
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Test compile sırasında hata. Tenant: {TenantId}", _currentTenant.Id);
+ throw;
+ }
+ }
+
+ ///
+ /// AppService'i yayınlar
+ ///
+ [Authorize(PlatformConsts.DynamicServices.Publish)]
+ public async Task PublishAsync(PublishAppServiceRequestDto request)
+ {
+ Logger.LogInformation("AppService yayınlama başlatıldı. Ad: {Name}, Tenant: {TenantId}",
+ request.Name, _currentTenant.Id);
+
+ try
+ {
+ // Önce kodu test compile et
+ var compileResult = await _compiler.CompileAndValidateAsync(request.Code, _currentTenant.Id);
+ if (!compileResult.Success)
+ {
+ return new PublishResultDto
+ {
+ Success = false,
+ ErrorMessage = compileResult.ErrorMessage
+ };
+ }
+
+ // Aynı isimde AppService var mı kontrol et
+ var existingService = await _dynamicAppServiceRepository
+ .FirstOrDefaultAsync(x => x.Name == request.Name && x.TenantId == _currentTenant.Id);
+
+ DynamicService appService;
+
+ if (existingService != null)
+ {
+ // Mevcut servisi güncelle
+ existingService.UpdateCode(request.Code);
+ existingService.DisplayName = request.DisplayName;
+ existingService.Description = request.Description;
+ existingService.PrimaryEntityType = request.PrimaryEntityType;
+
+ appService = await _dynamicAppServiceRepository.UpdateAsync(existingService);
+ Logger.LogInformation("Mevcut AppService güncellendi. ID: {Id}", appService.Id);
+ }
+ else
+ {
+ // Yeni servis oluştur
+ appService = new DynamicService(
+ GuidGenerator.Create(),
+ request.Name,
+ request.Code,
+ _currentTenant.Id)
+ {
+ DisplayName = request.DisplayName,
+ Description = request.Description,
+ PrimaryEntityType = request.PrimaryEntityType,
+ ControllerName = GenerateControllerName(request.Name)
+ };
+
+ appService = await _dynamicAppServiceRepository.InsertAsync(appService);
+ Logger.LogInformation("Yeni AppService oluşturuldu. ID: {Id}", appService.Id);
+ }
+
+ // Runtime'da derle ve yükle
+ var assemblyName = $"DynamicAppService_{appService.Id}_{appService.Version}";
+ var loadResult = await _compiler.CompileAndRegisterForTenantAsync(
+ _currentTenant.Id ?? Guid.Empty,
+ request.Code,
+ assemblyName);
+
+ if (loadResult.Success)
+ {
+ // Başarılı derleme durumunu kaydet
+ appService.MarkCompilationSuccess();
+ await _dynamicAppServiceRepository.UpdateAsync(appService);
+
+ var result = new PublishResultDto
+ {
+ Success = true,
+ AppServiceId = appService.Id,
+ ControllerName = appService.ControllerName,
+ SwaggerUrl = "/swagger/index.html",
+ GeneratedEndpoints = GenerateEndpointList(request.Name)
+ };
+
+ Logger.LogInformation("AppService başarıyla yayınlandı. ID: {Id}, Controller: {Controller}",
+ appService.Id, appService.ControllerName);
+
+ return result;
+ }
+ else
+ {
+ // Derleme hatası durumunu kaydet
+ appService.MarkCompilationError(loadResult.ErrorMessage);
+ await _dynamicAppServiceRepository.UpdateAsync(appService);
+
+ return new PublishResultDto
+ {
+ Success = false,
+ ErrorMessage = loadResult.ErrorMessage,
+ AppServiceId = appService.Id
+ };
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "AppService yayınlama sırasında hata. Ad: {Name}, Tenant: {TenantId}",
+ request.Name, _currentTenant.Id);
+
+ return new PublishResultDto
+ {
+ Success = false,
+ ErrorMessage = $"Yayınlama sırasında beklenmeyen hata: {ex.Message}"
+ };
+ }
+ }
+
+ ///
+ /// AppService listesini getirir
+ ///
+ [Authorize(PlatformConsts.DynamicServices.DynamicAppServices)]
+ public async Task> GetListAsync(PagedAndSortedResultRequestDto input)
+ {
+ Logger.LogDebug("AppService listesi istendi. Tenant: {TenantId}", _currentTenant.Id);
+
+ var queryable = await _dynamicAppServiceRepository.GetQueryableAsync();
+
+ // Tenant filtresi otomatik uygulanır (IMultiTenant)
+ var query = queryable.WhereIf(!string.IsNullOrEmpty(input.Sorting),
+ x => x.CreationTime.ToString().Contains(input.Sorting ?? ""));
+
+ var totalCount = await AsyncExecuter.CountAsync(query);
+
+ var items = await AsyncExecuter.ToListAsync(
+ query.OrderByDescending(x => x.CreationTime)
+ .Skip(input.SkipCount)
+ .Take(input.MaxResultCount)
+ );
+
+ var dtos = ObjectMapper.Map, List>(items);
+
+ Logger.LogDebug("AppService listesi döndürüldü. Toplam: {Total}, Dönen: {Count}",
+ totalCount, dtos.Count);
+
+ return new PagedResultDto(totalCount, dtos);
+ }
+
+ ///
+ /// Belirli bir AppService'i getirir
+ ///
+ [Authorize(PlatformConsts.DynamicServices.ViewCode)]
+ public async Task GetAsync(Guid id)
+ {
+ Logger.LogDebug("AppService detayı istendi. ID: {Id}, Tenant: {TenantId}", id, _currentTenant.Id);
+
+ var appService = await _dynamicAppServiceRepository.GetAsync(id);
+ var dto = ObjectMapper.Map(appService);
+
+ return dto;
+ }
+
+ ///
+ /// AppService'i siler
+ ///
+ [Authorize(PlatformConsts.DynamicServices.Delete)]
+ public async Task DeleteAsync(Guid id)
+ {
+ Logger.LogInformation("AppService silme işlemi başlatıldı. ID: {Id}, Tenant: {TenantId}",
+ id, _currentTenant.Id);
+
+ var appService = await _dynamicAppServiceRepository.GetAsync(id);
+
+ // TODO: Runtime'dan assembly'yi kaldırma işlemi
+ // (AssemblyLoadContext.Unload() çağrısı)
+
+ await _dynamicAppServiceRepository.DeleteAsync(id);
+
+ Logger.LogInformation("AppService silindi. ID: {Id}", id);
+ }
+
+ ///
+ /// AppService'i aktif/pasif yapar
+ ///
+ [Authorize(PlatformConsts.DynamicServices.Manage)]
+ public async Task SetActiveAsync(Guid id, bool isActive)
+ {
+ Logger.LogInformation("AppService aktiflik durumu değiştiriliyor. ID: {Id}, Aktif: {Active}",
+ id, isActive);
+
+ var appService = await _dynamicAppServiceRepository.GetAsync(id);
+ appService.IsActive = isActive;
+
+ await _dynamicAppServiceRepository.UpdateAsync(appService);
+
+ Logger.LogInformation("AppService aktiflik durumu güncellendi. ID: {Id}, Aktif: {Active}",
+ id, isActive);
+ }
+
+ ///
+ /// Tüm aktif AppService'leri yeniden yükler
+ ///
+ [Authorize(PlatformConsts.DynamicServices.Manage)]
+ public async Task ReloadAllActiveServicesAsync()
+ {
+ Logger.LogInformation("Tüm aktif AppService'ler yeniden yükleniyor. Tenant: {TenantId}",
+ _currentTenant.Id);
+
+ var activeServices = await _dynamicAppServiceRepository
+ .GetListAsync(x => x.IsActive && x.CompilationStatus == CompilationStatus.Success);
+
+ var successCount = 0;
+ var errorCount = 0;
+
+ foreach (var service in activeServices)
+ {
+ try
+ {
+ var assemblyName = $"DynamicAppService_{service.Id}_{service.Version}";
+ var result = await _compiler.CompileAndRegisterForTenantAsync(
+ _currentTenant.Id ?? Guid.Empty,
+ service.Code,
+ assemblyName);
+
+ if (result.Success)
+ {
+ successCount++;
+ Logger.LogDebug("AppService yeniden yüklendi. ID: {Id}, Ad: {Name}",
+ service.Id, service.Name);
+ }
+ else
+ {
+ errorCount++;
+ service.MarkCompilationError(result.ErrorMessage);
+ await _dynamicAppServiceRepository.UpdateAsync(service);
+
+ Logger.LogWarning("AppService yeniden yüklenemedi. ID: {Id}, Hata: {Error}",
+ service.Id, result.ErrorMessage);
+ }
+ }
+ catch (Exception ex)
+ {
+ errorCount++;
+ Logger.LogError(ex, "AppService yeniden yükleme hatası. ID: {Id}", service.Id);
+
+ service.MarkCompilationError(ex.Message);
+ await _dynamicAppServiceRepository.UpdateAsync(service);
+ }
+ }
+
+ Logger.LogInformation("AppService yeniden yükleme tamamlandı. Başarılı: {Success}, Hatalı: {Error}, Toplam: {Total}",
+ successCount, errorCount, activeServices.Count);
+ }
+
+ ///
+ /// Belirli bir AppService'i yeniden derler
+ ///
+ [Authorize(PlatformConsts.DynamicServices.Manage)]
+ public async Task RecompileAsync(Guid id)
+ {
+ Logger.LogInformation("AppService yeniden derleniyor. ID: {Id}", id);
+
+ var appService = await _dynamicAppServiceRepository.GetAsync(id);
+
+ var assemblyName = $"DynamicAppService_{appService.Id}_{appService.Version + 1}";
+ var result = await _compiler.CompileAndRegisterForTenantAsync(
+ _currentTenant.Id ?? Guid.Empty,
+ appService.Code,
+ assemblyName);
+
+ if (result.Success)
+ {
+ appService.MarkCompilationSuccess();
+ appService.Version++;
+ }
+ else
+ {
+ appService.MarkCompilationError(result.ErrorMessage);
+ }
+
+ await _dynamicAppServiceRepository.UpdateAsync(appService);
+
+ Logger.LogInformation("AppService yeniden derleme tamamlandı. ID: {Id}, Başarılı: {Success}",
+ id, result.Success);
+
+ return result;
+ }
+
+ ///
+ /// Controller adını oluşturur
+ ///
+ private string GenerateControllerName(string serviceName)
+ {
+ // DynamicCustomerAppService -> DynamicCustomer
+ var controllerName = serviceName;
+ if (controllerName.EndsWith("AppService"))
+ {
+ controllerName = controllerName.Substring(0, controllerName.Length - "AppService".Length);
+ }
+ else if (controllerName.EndsWith("ApplicationService"))
+ {
+ controllerName = controllerName.Substring(0, controllerName.Length - "ApplicationService".Length);
+ }
+
+ return controllerName;
+ }
+
+ ///
+ /// Örnek endpoint listesi oluşturur
+ ///
+ private List GenerateEndpointList(string serviceName)
+ {
+ var controllerName = GenerateControllerName(serviceName);
+
+ return new List
+ {
+ $"/api/app/{controllerName.ToLowerInvariant()}",
+ $"/api/app/{controllerName.ToLowerInvariant()}/{{id}}",
+ $"/api/app/{controllerName.ToLowerInvariant()}/list"
+ };
+ }
+}
diff --git a/api/src/Kurs.Platform.Application/Kurs.Platform.Application.csproj b/api/src/Kurs.Platform.Application/Kurs.Platform.Application.csproj
index 4a184c1b..deef89c2 100644
--- a/api/src/Kurs.Platform.Application/Kurs.Platform.Application.csproj
+++ b/api/src/Kurs.Platform.Application/Kurs.Platform.Application.csproj
@@ -26,6 +26,9 @@
+
+
+
diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json
index c2d645e1..754836e3 100644
--- a/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json
+++ b/api/src/Kurs.Platform.DbMigrator/Seeds/LanguagesData.json
@@ -9937,6 +9937,12 @@
"en": "fields",
"tr": "alan"
},
+ {
+ "resourceName": "Platform",
+ "key": "App.DeveloperKit.DynamicServices",
+ "en": "Dynamic Services",
+ "tr": "Dinamik Hizmetler"
+ },
{
"resourceName": "Platform",
"key": "App.DeveloperKit.Migration.Generating",
diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json
index e8ddeddf..9e5ec69c 100644
--- a/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json
+++ b/api/src/Kurs.Platform.DbMigrator/Seeds/MenusData.json
@@ -350,6 +350,15 @@
"App.DeveloperKit.Endpoints"
]
},
+ {
+ "key": "admin.developerkit.dynamic-services",
+ "path": "/admin/developerkit/dynamic-services",
+ "componentPath": "@/views/developerKit/DynamicServicePage",
+ "routeType": "protected",
+ "authority": [
+ "App.DeveloperKit.DynamicServices"
+ ]
+ },
{
"key": "admin.developerkit.components",
"path": "/admin/developerkit/components",
@@ -1646,6 +1655,16 @@
"RequiredPermissionName": "App.DeveloperKit.CustomEndpoints",
"IsDisabled": false
},
+ {
+ "ParentCode": "App.DeveloperKit",
+ "Code": "App.DeveloperKit.DynamicServices",
+ "DisplayName": "App.DeveloperKit.DynamicServices",
+ "Order": 5,
+ "Url": "/admin/developerkit/dynamic-services",
+ "Icon": "FcCommandLine",
+ "RequiredPermissionName": "App.DeveloperKit.DynamicServices",
+ "IsDisabled": false
+ },
{
"ParentCode": "App.DeveloperKit",
"Code": "App.DeveloperKit.Components",
diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/PermissionsData.json b/api/src/Kurs.Platform.DbMigrator/Seeds/PermissionsData.json
index a31eb6ef..3229e9e1 100644
--- a/api/src/Kurs.Platform.DbMigrator/Seeds/PermissionsData.json
+++ b/api/src/Kurs.Platform.DbMigrator/Seeds/PermissionsData.json
@@ -2335,6 +2335,78 @@
"MultiTenancySide": 3,
"MenuGroup": "Erp|Kurs"
},
+ {
+ "GroupName": "App.Administration",
+ "Name": "App.DeveloperKit.DynamicServices",
+ "ParentName": "App.DeveloperKit",
+ "DisplayName": "App.DeveloperKit.DynamicServices",
+ "IsEnabled": true,
+ "MultiTenancySide": 3,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Administration",
+ "Name": "App.DeveloperKit.DynamicServices.Create",
+ "ParentName": "App.DeveloperKit.DynamicServices",
+ "DisplayName": "Create",
+ "IsEnabled": true,
+ "MultiTenancySide": 3,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Administration",
+ "Name": "App.DeveloperKit.DynamicServices.Update",
+ "ParentName": "App.DeveloperKit.DynamicServices",
+ "DisplayName": "Update",
+ "IsEnabled": true,
+ "MultiTenancySide": 3,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Administration",
+ "Name": "App.DeveloperKit.DynamicServices.Delete",
+ "ParentName": "App.DeveloperKit.DynamicServices",
+ "DisplayName": "Delete",
+ "IsEnabled": true,
+ "MultiTenancySide": 3,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Administration",
+ "Name": "App.DeveloperKit.DynamicServices.Manage",
+ "ParentName": "App.DeveloperKit.DynamicServices",
+ "DisplayName": "Manage",
+ "IsEnabled": true,
+ "MultiTenancySide": 3,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Administration",
+ "Name": "App.DeveloperKit.DynamicServices.TestCompile",
+ "ParentName": "App.DeveloperKit.DynamicServices",
+ "DisplayName": "TestCompile",
+ "IsEnabled": true,
+ "MultiTenancySide": 3,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Administration",
+ "Name": "App.DeveloperKit.DynamicServices.Publish",
+ "ParentName": "App.DeveloperKit.DynamicServices",
+ "DisplayName": "Publish",
+ "IsEnabled": true,
+ "MultiTenancySide": 3,
+ "MenuGroup": "Erp|Kurs"
+ },
+ {
+ "GroupName": "App.Administration",
+ "Name": "App.DeveloperKit.DynamicServices.ViewCode",
+ "ParentName": "App.DeveloperKit.DynamicServices",
+ "DisplayName": "ViewCode",
+ "IsEnabled": true,
+ "MultiTenancySide": 3,
+ "MenuGroup": "Erp|Kurs"
+ },
{
"GroupName": "App.Administration",
"Name": "App.Reports.Categories",
diff --git a/api/src/Kurs.Platform.Domain.Shared/Enums/TableNameEnum.cs b/api/src/Kurs.Platform.Domain.Shared/Enums/TableNameEnum.cs
index bff559cd..6fde5742 100644
--- a/api/src/Kurs.Platform.Domain.Shared/Enums/TableNameEnum.cs
+++ b/api/src/Kurs.Platform.Domain.Shared/Enums/TableNameEnum.cs
@@ -145,5 +145,6 @@ public enum TableNameEnum
Partner,
PartnerBank,
PartnerCertificate,
- PartnerContact
+ PartnerContact,
+ DynamicService
}
\ No newline at end of file
diff --git a/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs
index 3c8f4adc..4c4e4de5 100644
--- a/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs
+++ b/api/src/Kurs.Platform.Domain.Shared/PlatformConsts.cs
@@ -71,6 +71,20 @@ public static class PlatformConsts
public const string MenuGroup = "MenuGroup";
}
+ public static class DynamicServices
+ {
+ public const string GroupName = "DynamicServices";
+
+ public const string DynamicAppServices = GroupName + ".DynamicAppServices";
+ public const string Create = DynamicAppServices + ".Create";
+ public const string Edit = DynamicAppServices + ".Edit";
+ public const string Delete = DynamicAppServices + ".Delete";
+ public const string Manage = DynamicAppServices + ".Manage";
+ public const string TestCompile = DynamicAppServices + ".TestCompile";
+ public const string Publish = DynamicAppServices + ".Publish";
+ public const string ViewCode = DynamicAppServices + ".ViewCode";
+ }
+
public static class AbpIdentity
{
public const string GroupName = $"{Prefix.Abp}.Identity";
diff --git a/api/src/Kurs.Platform.Domain.Shared/TableNameResolver.cs b/api/src/Kurs.Platform.Domain.Shared/TableNameResolver.cs
index 9c5d4384..365444a6 100644
--- a/api/src/Kurs.Platform.Domain.Shared/TableNameResolver.cs
+++ b/api/src/Kurs.Platform.Domain.Shared/TableNameResolver.cs
@@ -12,6 +12,7 @@ public static class TableNameResolver
_map = new(StringComparer.OrdinalIgnoreCase)
{
// 🔹 MODULE TABLOLARI
+ { nameof(TableNameEnum.DynamicService), (TablePrefix.PlatformByName, MenuPrefix.Platform) },
{ nameof(TableNameEnum.LogEntry), (TablePrefix.PlatformByName, MenuPrefix.Platform) },
{ nameof(TableNameEnum.Language), (TablePrefix.PlatformByName, MenuPrefix.Platform) },
{ nameof(TableNameEnum.LanguageKey), (TablePrefix.PlatformByName, MenuPrefix.Platform) },
diff --git a/api/src/Kurs.Platform.Domain/Entities/Tenant/Administration/DynamicService.cs b/api/src/Kurs.Platform.Domain/Entities/Tenant/Administration/DynamicService.cs
new file mode 100644
index 00000000..6cfa9d02
--- /dev/null
+++ b/api/src/Kurs.Platform.Domain/Entities/Tenant/Administration/DynamicService.cs
@@ -0,0 +1,174 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Volo.Abp;
+using Volo.Abp.Domain.Entities.Auditing;
+using Volo.Abp.MultiTenancy;
+
+namespace Kurs.Platform.Entities;
+
+///
+/// Tenant bazında dinamik olarak tanımlanmış AppService'lerin kod ve meta verilerini saklar
+///
+public class DynamicService : FullAuditedEntity, IMultiTenant
+{
+ ///
+ /// Tenant ID - IMultiTenant implementasyonu
+ ///
+ public Guid? TenantId { get; set; }
+
+ ///
+ /// AppService'in benzersiz adı (örn: "DynamicCustomerService")
+ ///
+ [Required]
+ [StringLength(256)]
+ public string Name { get; set; }
+
+ ///
+ /// AppService'in kullanıcı dostu başlığı
+ ///
+ [StringLength(512)]
+ public string? DisplayName { get; set; }
+
+ ///
+ /// AppService açıklaması
+ ///
+ [StringLength(2000)]
+ public string? Description { get; set; }
+
+ ///
+ /// Tam C# sınıf kodu (using'ler ve namespace dahil)
+ ///
+ [Required]
+ public string Code { get; set; }
+
+ ///
+ /// AppService aktif mi?
+ ///
+ public bool IsActive { get; set; }
+
+ ///
+ /// Son derleme durumu
+ ///
+ public CompilationStatus CompilationStatus { get; set; }
+
+ ///
+ /// Son derleme hatası (varsa)
+ ///
+ public string? LastCompilationError { get; set; }
+
+ ///
+ /// Son başarılı derleme zamanı
+ ///
+ public DateTime? LastSuccessfulCompilation { get; set; }
+
+ ///
+ /// Kod versiyonu (her güncelleme ile artan numara)
+ ///
+ public int Version { get; set; }
+
+ ///
+ /// Kod hash'i (değişiklik kontrolü için)
+ ///
+ [StringLength(64)]
+ public string? CodeHash { get; set; }
+
+ ///
+ /// AppService'in kullandığı ana entity türü (opsiyonel)
+ ///
+ [StringLength(256)]
+ public string? PrimaryEntityType { get; set; }
+
+ ///
+ /// Swagger'da görünecek controller adı
+ ///
+ [StringLength(256)]
+ public string? ControllerName { get; set; }
+
+ protected DynamicService()
+ {
+ // EF Core constructor
+ }
+
+ public DynamicService(
+ Guid id,
+ string name,
+ string code,
+ Guid? tenantId = null) : base(id)
+ {
+ Name = Check.NotNullOrWhiteSpace(name, nameof(name), maxLength: 256);
+ Code = Check.NotNullOrWhiteSpace(code, nameof(code));
+ TenantId = tenantId;
+ IsActive = true;
+ CompilationStatus = CompilationStatus.Pending;
+ Version = 1;
+ }
+
+ ///
+ /// Kodu günceller ve versiyonu artırır
+ ///
+ public void UpdateCode(string newCode)
+ {
+ Check.NotNullOrWhiteSpace(newCode, nameof(newCode));
+
+ if (Code != newCode)
+ {
+ Code = newCode;
+ Version++;
+ CompilationStatus = CompilationStatus.Pending;
+ LastCompilationError = null;
+ CodeHash = GenerateCodeHash(newCode);
+ }
+ }
+
+ ///
+ /// Derleme başarısını işaretler
+ ///
+ public void MarkCompilationSuccess()
+ {
+ CompilationStatus = CompilationStatus.Success;
+ LastCompilationError = null;
+ LastSuccessfulCompilation = DateTime.UtcNow;
+ }
+
+ ///
+ /// Derleme hatasını işaretler
+ ///
+ public void MarkCompilationError(string error)
+ {
+ CompilationStatus = CompilationStatus.Failed;
+ LastCompilationError = error;
+ }
+
+ private string GenerateCodeHash(string code)
+ {
+ using var sha256 = System.Security.Cryptography.SHA256.Create();
+ var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(code));
+ return Convert.ToHexString(hash);
+ }
+}
+
+///
+/// Derleme durumu enum'u
+///
+public enum CompilationStatus
+{
+ ///
+ /// Henüz derlenmedi
+ ///
+ Pending = 0,
+
+ ///
+ /// Derleme başarılı
+ ///
+ Success = 1,
+
+ ///
+ /// Derleme başarısız
+ ///
+ Failed = 2,
+
+ ///
+ /// Derleme işlemi devam ediyor
+ ///
+ InProgress = 3
+}
diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs
index fc676dff..2127639b 100644
--- a/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs
+++ b/api/src/Kurs.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs
@@ -26,6 +26,7 @@ using System.Text.Json;
using static Kurs.Platform.PlatformConsts;
using static Kurs.Settings.SettingsConsts;
using Kurs.Platform.Enums;
+using Kurs.Platform.Domain.DeveloperKit;
namespace Kurs.Platform.EntityFrameworkCore;
@@ -63,6 +64,7 @@ public class PlatformDbContext :
public DbSet GeneratedEndpoints { get; set; }
public DbSet CustomEndpoints { get; set; }
public DbSet CustomComponents { get; set; }
+ public DbSet DynamicServices { get; set; }
public DbSet ReportTemplates { get; set; }
public DbSet ReportParameters { get; set; }
public DbSet ReportGenerated { get; set; }
@@ -100,6 +102,8 @@ public class PlatformDbContext :
public DbSet Contacts { get; set; }
public DbSet Sectors { get; set; }
public DbSet SkillTypes { get; set; }
+
+ // Dynamic Services
public DbSet Skills { get; set; }
public DbSet SkillLevels { get; set; }
public DbSet UomCategories { get; set; }
@@ -2444,5 +2448,24 @@ public class PlatformDbContext :
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.LogEntry)), Prefix.DbSchema);
b.ConfigureByConvention();
});
+
+ builder.Entity(b =>
+ {
+ b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.DynamicService)), Prefix.DbSchema);
+ b.ConfigureByConvention();
+
+ b.Property(x => x.Name).IsRequired().HasMaxLength(256);
+ b.Property(x => x.DisplayName).HasMaxLength(512);
+ b.Property(x => x.Description).HasMaxLength(2000);
+ b.Property(x => x.Code).IsRequired().HasColumnType("text"); // Uzun C# kod metni için
+ b.Property(x => x.IsActive).IsRequired().HasDefaultValue(true);
+ b.Property(x => x.CompilationStatus).IsRequired().HasConversion().HasMaxLength(20);
+ b.Property(x => x.LastCompilationError).HasColumnType("text"); // Uzun hata mesajları için
+ b.Property(x => x.LastSuccessfulCompilation).IsRequired(false);
+ b.Property(x => x.Version).IsRequired().HasDefaultValue(1);
+ b.Property(x => x.CodeHash).HasMaxLength(64);
+ b.Property(x => x.PrimaryEntityType).HasMaxLength(256);
+ b.Property(x => x.ControllerName).HasMaxLength(256);
+ });
}
}
diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251104205840_DynamicService.Designer.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251104205840_DynamicService.Designer.cs
new file mode 100644
index 00000000..436eed89
--- /dev/null
+++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251104205840_DynamicService.Designer.cs
@@ -0,0 +1,13841 @@
+//
+using System;
+using Kurs.Platform.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Volo.Abp.EntityFrameworkCore;
+
+#nullable disable
+
+namespace Kurs.Platform.Migrations
+{
+ [DbContext(typeof(PlatformDbContext))]
+ [Migration("20251104205840_DynamicService")]
+ partial class DynamicService
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
+ .HasAnnotation("ProductVersion", "9.0.2")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Kurs.Languages.Entities.Language", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("CultureName")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("IsEnabled")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("TwoLetterISOLanguageName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UiCultureName")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Plat_H_Language", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Languages.Entities.LanguageKey", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("Key")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("ResourceName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ResourceName", "Key")
+ .IsUnique();
+
+ b.ToTable("Plat_H_LanguageKey", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Languages.Entities.LanguageText", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("CultureName")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("Key")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("ResourceName")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CultureName");
+
+ b.HasIndex("ResourceName", "Key");
+
+ b.ToTable("Plat_H_LanguageText", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.MailQueue.Domain.Entities.BackgroundWorker_MailQueue", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Attachment")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("AttachmentParameter")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("AwsMessageId")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("From")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("MailParameter")
+ .HasMaxLength(8000)
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RelatedRecordId")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("SendStatus")
+ .HasColumnType("bit");
+
+ b.Property("SendTime")
+ .HasColumnType("datetime2");
+
+ b.Property("TableName")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("TableParameter")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("TemplateId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("To")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TableName");
+
+ b.ToTable("Plat_H_BackgroundWorker_MailQueue", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.MailQueue.Domain.Entities.BackgroundWorker_MailQueueEvents", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("AwsMessageId")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("Event")
+ .HasMaxLength(20)
+ .HasColumnType("nvarchar(20)");
+
+ b.Property("EventDate")
+ .HasColumnType("datetime2");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("MailAddress")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("ResponseDescription")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AwsMessageId");
+
+ b.ToTable("Plat_H_BackgroundWorker_MailQueueEvents", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.MailQueue.Domain.Entities.BackgroundWorker_MailQueueTableFormat", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Caption")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("ColumnName")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("Css")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("DataFormat")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("DataType")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("FooterCss")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("HeaderCss")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("IsHidden")
+ .HasColumnType("bit");
+
+ b.Property("IsProtected")
+ .HasColumnType("bit");
+
+ b.Property("Order")
+ .HasColumnType("smallint");
+
+ b.Property("SubTotal")
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("TableName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Width")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TableName", "Order")
+ .IsUnique()
+ .HasDatabaseName("IX_MailQueueTableFormat");
+
+ b.ToTable("Plat_H_BackgroundWorker_MailQueueTableFormat", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Notifications.Entities.Notification", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("Identifier")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("IsRead")
+ .HasColumnType("bit");
+
+ b.Property("IsSent")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("NotificationChannel")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("NotificationRuleId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("NotificationType")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("ReadTime")
+ .HasColumnType("datetime2");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NotificationRuleId");
+
+ b.ToTable("Plat_H_Notification", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Notifications.Entities.NotificationRule", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Channel")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("IsCustomized")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("IsFixed")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("NotificationType")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("RecipientId")
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("RecipientType")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Plat_H_NotificationRule", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Platform.Entities.About", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("DescriptionsJson")
+ .HasColumnType("text");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("SectionsJson")
+ .HasColumnType("text");
+
+ b.Property("StatsJson")
+ .HasColumnType("text");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.ToTable("Adm_T_About", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Platform.Entities.Activity", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("EntityId")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("EntityName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FilesJson")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("Subject")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("nvarchar(256)");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Sas_T_Activity", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Platform.Entities.AiBot", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("BotName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("nvarchar(128)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Plat_H_AiBot", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Platform.Entities.Announcement", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Attachments")
+ .HasMaxLength(2000)
+ .HasColumnType("nvarchar(2000)");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .HasColumnType("nvarchar(4000)");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("Departments")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("EmployeeId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Excerpt")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("ExpiryDate")
+ .HasColumnType("datetime2");
+
+ b.Property("ImageUrl")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("IsPinned")
+ .HasColumnType("bit");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("PublishDate")
+ .HasColumnType("datetime2");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("ViewCount")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int")
+ .HasDefaultValue(0);
+
+ b.HasKey("Id");
+
+ b.HasIndex("EmployeeId");
+
+ b.ToTable("Net_T_Announcement", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Platform.Entities.ApiEndpoint", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("CsharpCode")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DeleterId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("DeleterId");
+
+ b.Property("DeletionTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("DeletionTime");
+
+ b.Property("EntityId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EntityName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bit")
+ .HasDefaultValue(false)
+ .HasColumnName("IsDeleted");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("Method")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("OperationType")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("Path")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EntityId");
+
+ b.ToTable("Sas_T_ApiEndpoint", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Platform.Entities.ApiMigration", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("AppliedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property("EntityId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("EntityName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("nvarchar(200)");
+
+ b.Property("LastModificationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("LastModificationTime");
+
+ b.Property("LastModifierId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("LastModifierId");
+
+ b.Property("SqlScript")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("TenantId");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EntityId");
+
+ b.ToTable("Sas_T_ApiMigration", (string)null);
+ });
+
+ modelBuilder.Entity("Kurs.Platform.Entities.BackgroundWorker", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("AfterSp")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("BeforeSp")
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("CreationTime")
+ .HasColumnType("datetime2")
+ .HasColumnName("CreationTime");
+
+ b.Property("CreatorId")
+ .HasColumnType("uniqueidentifier")
+ .HasColumnName("CreatorId");
+
+ b.Property