using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Sozsoft.Platform.Entities; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; namespace Sozsoft.Platform.Data.Seeds; /// /// SqlQueryManager uzerinden uretilen CRUD endpoint'lerini /// {host|tenants/{tenantId}}/crud/*.json dosyalarindan okuyarak veritabanina uygular. /// /// tablolari olusturduktan hemen sonra calisir; boylece endpoint'ler /// her zaman dayandiklari tablolar hazir olduktan sonra yazilir. ABP'nin contributor sirasi /// garanti edilmedigi icin bilincli olarak degildir, /// SqlDataSeeder tarafindan dogrudan cagrilir. /// /// Her dosya bir entity'ye aittir ve o entity'nin endpoint listesinin tamamini icerir: /// dosyadaki liste kaynak kabul edilir, veritabanindaki eski kayitlar dosyaya gore guncellenir. /// public class CrudDataSeeder : ITransientDependency { private static readonly JsonSerializerOptions JsonReadOptions = new() { PropertyNameCaseInsensitive = true }; private readonly IRepository _repository; private readonly IConfiguration _configuration; private readonly ILogger _logger; public CrudDataSeeder( IRepository repository, IConfiguration configuration, ILogger logger) { _repository = repository; _configuration = configuration; _logger = logger; } public async Task SeedAsync(DataSeedContext context) { var scopeFolderName = SeedPathResolver.GetScopeFolderName(context.TenantId); var crudDataPath = SeedPathResolver.GetScopePath(_configuration, context.TenantId, SeedPathResolver.CrudFolder); if (!Directory.Exists(crudDataPath)) { _logger.LogInformation("Seeds/{Scope}/crud directory not found, skipping CrudDataSeeder.", scopeFolderName); return; } var jsonFiles = Directory.GetFiles(crudDataPath, "*.json").OrderBy(Path.GetFileName).ToArray(); if (jsonFiles.Length == 0) { _logger.LogInformation("No JSON files found in Seeds/{Scope}/crud directory, skipping CrudDataSeeder.", scopeFolderName); return; } _logger.LogInformation("CrudDataSeeder started. {Count} file(s) to be processed.", jsonFiles.Length); foreach (var filePath in jsonFiles) { var fileName = Path.GetFileName(filePath); try { var json = await File.ReadAllTextAsync(filePath); var seedFile = JsonSerializer.Deserialize(json, JsonReadOptions); var entityName = string.IsNullOrWhiteSpace(seedFile?.EntityName) ? Path.GetFileNameWithoutExtension(filePath) : seedFile!.EntityName.Trim(); if (seedFile?.Endpoints == null || seedFile.Endpoints.Count == 0) { _logger.LogWarning("[{File}] No endpoint found, skipped.", fileName); continue; } await ApplyAsync(entityName, seedFile.Endpoints); _logger.LogInformation("[{File}] '{EntityName}' applied with {Count} endpoint(s).", fileName, entityName, seedFile.Endpoints.Count); } catch (Exception ex) { // Tek bir dosyanin hatasi diger endpoint'lerin seed edilmesini engellemez. _logger.LogError(ex, "Failed to process CRUD seed file: {FileName}", fileName); } } _logger.LogInformation("CrudDataSeeder completed. {Count} file(s) processed.", jsonFiles.Length); } private async Task ApplyAsync(string entityName, List endpoints) { var existing = await _repository.GetListAsync(x => x.EntityName == entityName); foreach (var item in endpoints) { if (string.IsNullOrWhiteSpace(item.OperationType)) { continue; } var current = existing.FirstOrDefault(x => string.Equals(x.OperationType, item.OperationType, StringComparison.OrdinalIgnoreCase)); if (current == null) { // CsharpCode seed dosyasinda tutulmaz; endpoint'in calismasi icin gerekli degildir, // gerektiginde SqlQueryManager'daki "Yeniden Olustur" ile uretilir. await _repository.InsertAsync(new CrudEndpoint { EntityName = entityName, Method = item.Method, Path = item.Path, OperationType = item.OperationType, IsActive = item.IsActive }, autoSave: true); continue; } current.Method = item.Method; current.Path = item.Path; current.IsActive = item.IsActive; await _repository.UpdateAsync(current, autoSave: true); } // Dosyada artik yer almayan operasyonlar kaldirilir; dosya tek kaynaktir. var seededOperations = endpoints .Select(x => x.OperationType) .Where(x => !string.IsNullOrWhiteSpace(x)) .ToHashSet(StringComparer.OrdinalIgnoreCase); var obsolete = existing.Where(x => !seededOperations.Contains(x.OperationType)).ToList(); if (obsolete.Count > 0) { await _repository.DeleteManyAsync(obsolete, autoSave: true); } } }