diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json index f4da501..44be831 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json @@ -1066,6 +1066,14 @@ "workerType": "NotificationWorker", "isActive": true, "dataSourceCode": "Default" + }, + { + "name": "Backup Databases", + "cron": "0 1 * * *", + "workerType": "BackupWorker", + "isActive": true, + "dataSourceCode": "Default", + "beforeSp": "Adm_T_DatabaseBackup" } ], "ContactTitles": [ diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs index 22b5c86..279910e 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs @@ -4749,9 +4749,9 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency new EditingFormItemDto { Order = 1, DataField = "Name", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order = 2, DataField = "Cron", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order = 3, DataField = "WorkerType", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, - new EditingFormItemDto { Order = 4, DataField = "BeforeSp", ColSpan = 1, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 5, DataField = "AfterSp", ColSpan = 1, EditorType2=EditorTypes.dxTextBox }, - new EditingFormItemDto { Order = 6, DataField = "IsActive", ColSpan = 1, EditorType2=EditorTypes.dxCheckBox }, + new EditingFormItemDto { Order = 4, DataField = "IsActive", ColSpan = 1, EditorType2=EditorTypes.dxCheckBox }, + new EditingFormItemDto { Order = 5, DataField = "BeforeSp", ColSpan = 1, EditorType2=EditorTypes.dxTextBox }, + new EditingFormItemDto { Order = 6, DataField = "AfterSp", ColSpan = 1, EditorType2=EditorTypes.dxTextBox }, ] }, new() { Order=2,ColCount=2,ColSpan=1,ItemType="group",Items= [ @@ -4841,6 +4841,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency new() { Key=2,Name="SqlWorker" }, new() { Key=3,Name="NotificationWorker" }, new() { Key=4,Name="SessionCleanupWorker" }, + new() { Key=5,Name="BackupWorker" }, }), }), ValidationRuleJson = DefaultValidationRuleRequiredJson, diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/WorkerTypeEnum.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/WorkerTypeEnum.cs index 3fd9cb9..94b5ae8 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/WorkerTypeEnum.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/WorkerTypeEnum.cs @@ -6,5 +6,6 @@ public enum WorkerTypeEnum SqlWorker = 2, NotificationWorker = 3, SessionCleanupWorker = 4, + BackupWorker = 5, } diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs index c279185..474fd60 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/PlatformBackgroundWorker.cs @@ -18,6 +18,8 @@ using Volo.Abp.ExceptionHandling; using Volo.Abp.ObjectMapping; using Volo.Abp.Settings; using Volo.Abp.Users; +using Microsoft.Extensions.Configuration; +using System.IO; namespace Sozsoft.Platform.BackgroundWorkers; @@ -28,6 +30,7 @@ public interface IPlatformBackgroundWorker : ITransientDependency public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgroundWorker { + private readonly IConfiguration Configuration; protected IAbpDistributedLock DistributedLock { get; } protected ISpRepository SpRepository { get; } public IDynamicDataManager DynamicDataManager { get; } @@ -41,11 +44,13 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou ICurrentUser currentUser, IObjectMapper objectMapper, IDynamicDataManager dynamicDataManager, + IConfiguration configuration, IRepository repository) : base(settingProvider, localizer, currentUser, objectMapper) { DistributedLock = distributedLock; SpRepository = spRepository; DynamicDataManager = dynamicDataManager; + Configuration = configuration; Repository = repository; } @@ -55,6 +60,8 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou var LogPrefix = $"{Clock.Now:s}_{Worker.Name}: {{0}}"; var DistributedLockName = Worker.Name; + var backupPath = ""; + var backupDeleteAfterDays = Configuration.GetValue("App:BackupDeleteAfterDays", 3); //using (var scope = ServiceScopeFactory.CreateScope()) @@ -70,6 +77,16 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou try { + // Backup Worker için yedeklerin saklanacağı klasörün oluşturulması + if (Worker.WorkerType == WorkerTypeEnum.BackupWorker) + { + backupPath = Path.Combine(Configuration["App:CdnPath"], "host", "backup"); + if (!Directory.Exists(backupPath)) + { + Directory.CreateDirectory(backupPath); + } + } + // Call BeforeSp if (!Worker.BeforeSp.IsNullOrWhiteSpace()) { @@ -87,7 +104,7 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou worker.Options = JsonSerializer.Deserialize(Worker.Options); await worker.StartAsync(cancellationToken); } - else if (Worker.WorkerType == WorkerTypeEnum.SqlWorker) + else if (Worker.WorkerType == WorkerTypeEnum.SqlWorker || Worker.WorkerType == WorkerTypeEnum.BackupWorker) { await LazyServiceProvider.GetRequiredService().StartAsync(cancellationToken); } @@ -107,6 +124,20 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou var result = await SpRepository.CallSpAsync(Worker.AfterSp, Worker.DataSourceCode, null); Logger.LogInformation(LogPrefix, $"AfterSp çalıştırıldı. Sonuç: {result}"); } + + // Backup Worker için yedekleme işlemi ve eski yedeklerin silinmesi + if (Worker.WorkerType == WorkerTypeEnum.BackupWorker) + { + var files = Directory.GetFiles(backupPath, "*.bak"); + + foreach (var file in files) + { + if (File.GetLastWriteTime(file) < DateTime.Now.AddDays(backupDeleteAfterDays)) + { + File.Delete(file); + } + } + } } catch (Exception ex) { diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTables.sql b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTables.sql index 50db692..9e33daf 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTables.sql +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTables.sql @@ -29,4 +29,24 @@ IF NOT EXISTS ( BEGIN ALTER TABLE [dbo].[Adm_T_Behavior] ADD DEFAULT (CONVERT([bit],(0))) FOR [IsDeleted] END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Adm_T_DatabaseBackup] +AS +BEGIN + SET NOCOUNT ON; + + DECLARE @SQL NVARCHAR(MAX) = N''; + + SELECT @SQL = @SQL + N' +BACKUP DATABASE ' + QUOTENAME(name) + N' +TO DISK = N''/var/opt/mssql/backup/' ++ name + N'_' + CONVERT(VARCHAR(8), GETDATE(), 112) + N'.bak'' +WITH INIT;' + FROM sys.databases + WHERE name NOT IN ('master','model','msdb','tempdb') + AND state_desc = 'ONLINE'; + + EXEC sp_executesql @SQL; +END GO \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTablesSeeder.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTablesSeeder.cs index 9f3be42..2fed0bb 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTablesSeeder.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/SqlTablesSeeder.cs @@ -55,15 +55,67 @@ public class SqlTablesSeeder : IDataSeedContributor, ITransientDependency try { + var (action, objectName, objectType) = ExtractSqlInfo(sql); + + if (!string.IsNullOrEmpty(objectName)) + { + _logger.LogInformation( + "Executing SQL: {Action} {ObjectType} {ObjectName}", + action, + objectType, + objectName); + } + else + { + _logger.LogInformation("Executing SQL batch (object not detected)"); + } + await dbContext.Database.ExecuteSqlRawAsync(sql); } catch (Exception ex) { - _logger.LogWarning("SQL batch skipped (may already exist): {Message}", ex.Message); + _logger.LogError(ex, "SQL batch failed: {Sql}", sql); + throw; } } _logger.LogInformation("Database script seeding completed successfully."); } + + private (string Action, string? ObjectName, string? ObjectType) ExtractSqlInfo(string sql) + { + var patterns = new[] + { + (@"CREATE\s+TABLE\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "TABLE"), + (@"ALTER\s+TABLE\s+(\[?\w+\]?\.?\[?\w+\]?)", "ALTER", "TABLE"), + + (@"CREATE\s+VIEW\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "VIEW"), + + (@"CREATE\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "PROCEDURE"), + (@"ALTER\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "ALTER", "PROCEDURE"), + (@"CREATE\s+OR\s+ALTER\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE/ALTER", "PROCEDURE"), + + (@"CREATE\s+FUNCTION\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "FUNCTION"), + + (@"CREATE\s+INDEX\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "INDEX"), + + (@"CREATE\s+TRIGGER\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "TRIGGER"), + + (@"DROP\s+TABLE\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "TABLE"), + (@"DROP\s+VIEW\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "VIEW"), + (@"DROP\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "PROCEDURE") + }; + + foreach (var (pattern, action, type) in patterns) + { + var match = Regex.Match(sql, pattern, RegexOptions.IgnoreCase); + if (match.Success) + { + return (action, match.Groups[1].Value, type); + } + } + + return ("UNKNOWN", null, null); + } } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj b/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj index 8771d48..0ee77f8 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Sozsoft.Platform.EntityFrameworkCore.csproj @@ -21,6 +21,7 @@ PreserveNewest Always + PreserveNewest Always diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json index 95d2470..cdd0b1a 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json +++ b/api/src/Sozsoft.Platform.HttpApi.Host/appsettings.Production.json @@ -6,7 +6,8 @@ "RedirectAllowedUrls": "https://sozsoft.com,https://sozsoft.com/authentication/callback", "AttachmentsPath": "/etc/api/mail-queue/attachments", "CdnPath": "/etc/api/cdn", - "BaseDomain": "sozsoft.com" + "BaseDomain": "sozsoft.com", + "BackupDeleteAfterDays": 3 }, "ConnectionStrings": { "SqlServer": "Server=sql;Database=Sozsoft;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;", diff --git a/configs/docker/cdn/host/Intranet/10e48e11-9333-4c38-a22f-a779aaa5b5e9.jpg b/configs/docker/cdn/host/Intranet/10e48e11-9333-4c38-a22f-a779aaa5b5e9.jpg deleted file mode 100644 index f379891..0000000 Binary files a/configs/docker/cdn/host/Intranet/10e48e11-9333-4c38-a22f-a779aaa5b5e9.jpg and /dev/null differ diff --git a/configs/docker/cdn/host/Intranet/1672066291523.jpeg b/configs/docker/cdn/host/Intranet/1672066291523.jpeg deleted file mode 100644 index 268adc1..0000000 Binary files a/configs/docker/cdn/host/Intranet/1672066291523.jpeg and /dev/null differ diff --git a/configs/docker/docker-compose-data.yml b/configs/docker/docker-compose-data.yml index 0625ce5..51052da 100644 --- a/configs/docker/docker-compose-data.yml +++ b/configs/docker/docker-compose-data.yml @@ -45,4 +45,5 @@ services: ports: - 1433:1433 volumes: - - mssql:/var/opt/mssql \ No newline at end of file + - mssql:/var/opt/mssql + - /etc/api/cdn/host/backup:/var/opt/mssql/backup \ No newline at end of file