From 6e067a28d8527726a2a17045bef0f0e3d3430ed5 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 23 Apr 2021 17:28:18 +0800 Subject: refactor: Refactor a lot. --- BackEnd/Timeline.Tests/Helpers/TestApplication.cs | 18 ------ BackEnd/Timeline.Tests/Helpers/TestDatabase.cs | 17 +++--- .../IntegratedTests/IntegratedTestBase.cs | 8 +++ BackEnd/Timeline/Program.cs | 15 +---- BackEnd/Timeline/Services/DatabaseBackupService.cs | 35 ----------- .../DatabaseManagement/DatabaseBackupService.cs | 37 ++++++++++++ .../DatabaseManagement/DatabaseCustomMigrator.cs | 56 +++++++++++++++++ .../DatabaseManagementService.cs | 34 +++++++++++ .../DatabaseManagement/IDatabaseCustomMigration.cs | 12 ++++ .../MigationServiceCollectionExtensions.cs | 16 +++++ .../TimelinePostContentToDataMigration.cs | 70 ++++++++++++++++++++++ .../Services/Migration/CustomMigrationManager.cs | 55 ----------------- .../Services/Migration/ICustomMigration.cs | 11 ---- .../MigationServiceCollectionExtensions.cs | 14 ----- .../TimelinePostContentToDataMigration.cs | 69 --------------------- BackEnd/Timeline/Startup.cs | 6 +- 16 files changed, 246 insertions(+), 227 deletions(-) delete mode 100644 BackEnd/Timeline/Services/DatabaseBackupService.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/DatabaseBackupService.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/DatabaseCustomMigrator.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/DatabaseManagementService.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/IDatabaseCustomMigration.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/MigationServiceCollectionExtensions.cs create mode 100644 BackEnd/Timeline/Services/DatabaseManagement/TimelinePostContentToDataMigration.cs delete mode 100644 BackEnd/Timeline/Services/Migration/CustomMigrationManager.cs delete mode 100644 BackEnd/Timeline/Services/Migration/ICustomMigration.cs delete mode 100644 BackEnd/Timeline/Services/Migration/MigationServiceCollectionExtensions.cs delete mode 100644 BackEnd/Timeline/Services/Migration/TimelinePostContentToDataMigration.cs diff --git a/BackEnd/Timeline.Tests/Helpers/TestApplication.cs b/BackEnd/Timeline.Tests/Helpers/TestApplication.cs index 22f5f006..fcb0882e 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestApplication.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestApplication.cs @@ -15,21 +15,12 @@ namespace Timeline.Tests.Helpers { public class TestApplication : IAsyncLifetime { - public TestDatabase Database { get; } - public IHost Host { get; private set; } = default!; public string WorkDir { get; private set; } = default!; - public TestApplication() - { - Database = new TestDatabase(false); - } - public async Task InitializeAsync() { - await Database.InitializeAsync(); - WorkDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(WorkDir); @@ -42,13 +33,6 @@ namespace Timeline.Tests.Helpers ["WorkDir"] = WorkDir }); }) - .ConfigureServices(services => - { - services.AddDbContext(options => - { - options.UseSqlite(Database.Connection); - }); - }) .ConfigureWebHost(webBuilder => { webBuilder @@ -64,8 +48,6 @@ namespace Timeline.Tests.Helpers Host.Dispose(); Directory.Delete(WorkDir, true); - - await Database.DisposeAsync(); } } } diff --git a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs index c51a94ba..d37aace4 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs @@ -12,11 +12,11 @@ namespace Timeline.Tests.Helpers { public class TestDatabase : IAsyncLifetime { - private readonly bool _createUser; + private readonly bool _init; - public TestDatabase(bool createUser = true) + public TestDatabase(bool init = true) { - _createUser = createUser; + _init = init; Connection = new SqliteConnection("Data Source=:memory:;"); } @@ -24,9 +24,11 @@ namespace Timeline.Tests.Helpers { await Connection.OpenAsync(); - using (var context = CreateContext()) + if (_init) { + using var context = CreateContext(); await context.Database.EnsureCreatedAsync(); + context.JwtToken.Add(new JwtTokenEntity { Key = JwtTokenGenerateHelper.GenerateKey() @@ -42,11 +44,8 @@ namespace Timeline.Tests.Helpers await context.SaveChangesAsync(); - if (_createUser) - { - var user = await userService.CreateUser("user", "userpw"); - await userService.ModifyUser(user.Id, new ModifyUserParams() { Nickname = "imuser" }); - } + var user = await userService.CreateUser("user", "userpw"); + await userService.ModifyUser(user.Id, new ModifyUserParams() { Nickname = "imuser" }); } } diff --git a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs index b700ada2..161e5d98 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/IntegratedTestBase.cs @@ -64,6 +64,14 @@ namespace Timeline.Tests.IntegratedTests } var userService = scope.ServiceProvider.GetRequiredService(); + + await userService.ModifyUser(await userService.GetUserIdByUsername("administrator"), new ModifyUserParams + { + Username = "admin", + Password = "adminpw", + Nickname = "administrator" + }); + foreach (var user in users) { var (username, password, nickname) = user; diff --git a/BackEnd/Timeline/Program.cs b/BackEnd/Timeline/Program.cs index 19fa6e37..e16c27c5 100644 --- a/BackEnd/Timeline/Program.cs +++ b/BackEnd/Timeline/Program.cs @@ -7,7 +7,7 @@ using System.Resources; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Services; -using Timeline.Services.Migration; +using Timeline.Services.DatabaseManagement; [assembly: NeutralResourcesLanguage("en")] @@ -19,18 +19,7 @@ namespace Timeline { var host = CreateWebHostBuilder(args).Build(); - var databaseBackupService = host.Services.GetRequiredService(); - databaseBackupService.BackupNow(); - - using (var scope = host.Services.CreateScope()) - { - var databaseContext = scope.ServiceProvider.GetRequiredService(); - await databaseContext.Database.MigrateAsync(); - var customMigrationManager = scope.ServiceProvider.GetRequiredService(); - await customMigrationManager.Migrate(); - } - - host.Run(); + await host.RunAsync(); } public static IHostBuilder CreateWebHostBuilder(string[] args) => diff --git a/BackEnd/Timeline/Services/DatabaseBackupService.cs b/BackEnd/Timeline/Services/DatabaseBackupService.cs deleted file mode 100644 index a76b2a0d..00000000 --- a/BackEnd/Timeline/Services/DatabaseBackupService.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Globalization; -using System.IO; - -namespace Timeline.Services -{ - public interface IDatabaseBackupService - { - void BackupNow(); - } - - public class DatabaseBackupService : IDatabaseBackupService - { - private readonly IPathProvider _pathProvider; - private readonly IClock _clock; - - public DatabaseBackupService(IPathProvider pathProvider, IClock clock) - { - _pathProvider = pathProvider; - _clock = clock; - } - - public void BackupNow() - { - var databasePath = _pathProvider.GetDatabaseFilePath(); - if (File.Exists(databasePath)) - { - var backupDirPath = _pathProvider.GetDatabaseBackupDirectory(); - Directory.CreateDirectory(backupDirPath); - var fileName = _clock.GetCurrentTime().ToString("yyyy-MM-ddTHH-mm-ss", CultureInfo.InvariantCulture); - var path = Path.Combine(backupDirPath, fileName); - File.Copy(databasePath, path); - } - } - } -} diff --git a/BackEnd/Timeline/Services/DatabaseManagement/DatabaseBackupService.cs b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseBackupService.cs new file mode 100644 index 00000000..c00b5f95 --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseBackupService.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.DatabaseManagement +{ + public interface IDatabaseBackupService + { + Task BackupAsync(CancellationToken cancellationToken = default); + } + + public class DatabaseBackupService : IDatabaseBackupService + { + private readonly DatabaseContext _database; + private readonly IPathProvider _pathProvider; + private readonly IClock _clock; + + public DatabaseBackupService(DatabaseContext database, IPathProvider pathProvider, IClock clock) + { + _database = database; + _pathProvider = pathProvider; + _clock = clock; + } + + public async Task BackupAsync(CancellationToken cancellationToken = default) + { + var backupDirPath = _pathProvider.GetDatabaseBackupDirectory(); + Directory.CreateDirectory(backupDirPath); + var fileName = _clock.GetCurrentTime().ToString("yyyy-MM-ddTHH-mm-ss", CultureInfo.InvariantCulture); + var path = Path.Combine(backupDirPath, fileName); + await _database.Database.ExecuteSqlInterpolatedAsync($"VACUUM INTO {path}", cancellationToken); + } + } +} diff --git a/BackEnd/Timeline/Services/DatabaseManagement/DatabaseCustomMigrator.cs b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseCustomMigrator.cs new file mode 100644 index 00000000..2180ad40 --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseCustomMigrator.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.DatabaseManagement +{ + public interface IDatabaseCustomMigrator + { + Task MigrateAsync(CancellationToken cancellationToken = default); + } + + public class DatabaseCustomMigrator : IDatabaseCustomMigrator + { + private IEnumerable _migrations; + private DatabaseContext _database; + + private ILogger _logger; + + public DatabaseCustomMigrator(IEnumerable migrations, DatabaseContext database, ILogger logger) + { + _migrations = migrations; + _database = database; + _logger = logger; + } + + public async Task MigrateAsync(CancellationToken cancellationToken = default) + { + foreach (var migration in _migrations) + { + var name = migration.GetName(); + var isApplied = await _database.Migrations.AnyAsync(m => m.Name == name, cancellationToken); + + _logger.LogInformation("Found custom migration '{0}'. Applied: {1}.", name, isApplied); + + if (!isApplied) + { + _logger.LogWarning("Begin custom migration '{0}'.", name); + + await using var transaction = await _database.Database.BeginTransactionAsync(cancellationToken); + + await migration.ExecuteAsync(_database, cancellationToken); + + _database.Migrations.Add(new MigrationEntity { Name = name }); + await _database.SaveChangesAsync(cancellationToken); + + await transaction.CommitAsync(cancellationToken); + + _logger.LogWarning("End custom migration '{0}'.", name); + } + } + } + } +} diff --git a/BackEnd/Timeline/Services/DatabaseManagement/DatabaseManagementService.cs b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseManagementService.cs new file mode 100644 index 00000000..4d54d3dc --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/DatabaseManagementService.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.DatabaseManagement +{ + public class DatabaseManagementService : IHostedService + { + private readonly DatabaseContext _database; + private readonly IDatabaseBackupService _backupService; + private readonly IDatabaseCustomMigrator _customMigrator; + + public DatabaseManagementService(DatabaseContext database, IDatabaseBackupService backupService, IDatabaseCustomMigrator customMigrator) + { + _database = database; + _backupService = backupService; + _customMigrator = customMigrator; + } + + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await _backupService.BackupAsync(cancellationToken); + await _database.Database.MigrateAsync(cancellationToken); + await _customMigrator.MigrateAsync(cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/BackEnd/Timeline/Services/DatabaseManagement/IDatabaseCustomMigration.cs b/BackEnd/Timeline/Services/DatabaseManagement/IDatabaseCustomMigration.cs new file mode 100644 index 00000000..7300ccbd --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/IDatabaseCustomMigration.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; + +namespace Timeline.Services.DatabaseManagement +{ + public interface IDatabaseCustomMigration + { + string GetName(); + Task ExecuteAsync(DatabaseContext database, CancellationToken cancellationToken = default); + } +} diff --git a/BackEnd/Timeline/Services/DatabaseManagement/MigationServiceCollectionExtensions.cs b/BackEnd/Timeline/Services/DatabaseManagement/MigationServiceCollectionExtensions.cs new file mode 100644 index 00000000..d1f9b51c --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/MigationServiceCollectionExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Timeline.Services.DatabaseManagement +{ + public static class DatabaseManagementServiceCollectionExtensions + { + public static IServiceCollection AddDatabaseManagementService(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddHostedService(); + return services; + } + } +} \ No newline at end of file diff --git a/BackEnd/Timeline/Services/DatabaseManagement/TimelinePostContentToDataMigration.cs b/BackEnd/Timeline/Services/DatabaseManagement/TimelinePostContentToDataMigration.cs new file mode 100644 index 00000000..605223f3 --- /dev/null +++ b/BackEnd/Timeline/Services/DatabaseManagement/TimelinePostContentToDataMigration.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore; +using SixLabors.ImageSharp; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Models; + +namespace Timeline.Services.DatabaseManagement +{ + public class TimelinePostContentToDataMigration : IDatabaseCustomMigration + { + private readonly IDataManager _dataManager; + + public TimelinePostContentToDataMigration(IDataManager dataManager) + { + _dataManager = dataManager; + } + + public string GetName() => "TimelinePostContentToData"; + + public async Task ExecuteAsync(DatabaseContext database, CancellationToken cancellationToken) + { +#pragma warning disable CS0618 + var postEntities = await database.TimelinePosts.ToListAsync(cancellationToken); + + foreach (var postEntity in postEntities) + { + if (postEntity.Content is null) + { + postEntity.Deleted = true; + } + else + { + if (postEntity.ContentType == "text") + { + var tag = await _dataManager.RetainEntry(Encoding.UTF8.GetBytes(postEntity.Content)); + database.TimelinePostData.Add(new TimelinePostDataEntity + { + DataTag = tag, + Kind = MimeTypes.TextPlain, + Index = 0, + PostId = postEntity.Id, + LastUpdated = postEntity.LastUpdated + }); + } + else + { + var data = await _dataManager.GetEntryAndCheck(postEntity.Content, "Old image content does not have corresponding data with the tag."); + var format = Image.DetectFormat(data); + database.TimelinePostData.Add(new TimelinePostDataEntity + { + DataTag = postEntity.Content, + Kind = format.DefaultMimeType, + Index = 0, + PostId = postEntity.Id, + LastUpdated = postEntity.LastUpdated + }); + } + } + postEntity.Content = null; + postEntity.ContentType = null; + postEntity.ExtraContent = null; + } + + await database.SaveChangesAsync(cancellationToken); +#pragma warning restore CS0618 + } + } +} diff --git a/BackEnd/Timeline/Services/Migration/CustomMigrationManager.cs b/BackEnd/Timeline/Services/Migration/CustomMigrationManager.cs deleted file mode 100644 index f6f156cc..00000000 --- a/BackEnd/Timeline/Services/Migration/CustomMigrationManager.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Timeline.Entities; - -namespace Timeline.Services.Migration -{ - public interface ICustomMigrationManager - { - Task Migrate(); - } - - public class CustomMigrationManager : ICustomMigrationManager - { - private IEnumerable _migrations; - private DatabaseContext _database; - - private ILogger _logger; - - public CustomMigrationManager(IEnumerable migrations, DatabaseContext database, ILogger logger) - { - _migrations = migrations; - _database = database; - _logger = logger; - } - - public async Task Migrate() - { - foreach (var migration in _migrations) - { - var name = migration.GetName(); - var did = await _database.Migrations.AnyAsync(m => m.Name == name); - - _logger.LogInformation("Found custom migration '{0}'. Did: {1}.", name, did); - - if (!did) - { - _logger.LogWarning("Begin custom migration '{0}'.", name); - - await using var transaction = await _database.Database.BeginTransactionAsync(); - - await migration.Execute(_database); - - _database.Migrations.Add(new MigrationEntity { Name = name }); - await _database.SaveChangesAsync(); - - await transaction.CommitAsync(); - - _logger.LogWarning("End custom migration '{0}'.", name); - } - } - } - } -} diff --git a/BackEnd/Timeline/Services/Migration/ICustomMigration.cs b/BackEnd/Timeline/Services/Migration/ICustomMigration.cs deleted file mode 100644 index 1f47df1e..00000000 --- a/BackEnd/Timeline/Services/Migration/ICustomMigration.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Threading.Tasks; -using Timeline.Entities; - -namespace Timeline.Services.Migration -{ - public interface ICustomMigration - { - string GetName(); - Task Execute(DatabaseContext database); - } -} diff --git a/BackEnd/Timeline/Services/Migration/MigationServiceCollectionExtensions.cs b/BackEnd/Timeline/Services/Migration/MigationServiceCollectionExtensions.cs deleted file mode 100644 index 0e6f6c0a..00000000 --- a/BackEnd/Timeline/Services/Migration/MigationServiceCollectionExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Timeline.Services.Migration -{ - public static class MigrationServiceCollectionExtensions - { - public static IServiceCollection AddCustomMigration(this IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - return services; - } - } -} \ No newline at end of file diff --git a/BackEnd/Timeline/Services/Migration/TimelinePostContentToDataMigration.cs b/BackEnd/Timeline/Services/Migration/TimelinePostContentToDataMigration.cs deleted file mode 100644 index bb7bf606..00000000 --- a/BackEnd/Timeline/Services/Migration/TimelinePostContentToDataMigration.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Text; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using SixLabors.ImageSharp; -using Timeline.Entities; -using Timeline.Models; - -namespace Timeline.Services.Migration -{ - public class TimelinePostContentToDataMigration : ICustomMigration - { - private readonly IDataManager _dataManager; - - public TimelinePostContentToDataMigration(IDataManager dataManager) - { - _dataManager = dataManager; - } - - public string GetName() => "TimelinePostContentToData"; - - public async Task Execute(DatabaseContext database) - { -#pragma warning disable CS0618 - var postEntities = await database.TimelinePosts.ToListAsync(); - - foreach (var postEntity in postEntities) - { - if (postEntity.Content is null) - { - postEntity.Deleted = true; - } - else - { - if (postEntity.ContentType == "text") - { - var tag = await _dataManager.RetainEntry(Encoding.UTF8.GetBytes(postEntity.Content)); - database.TimelinePostData.Add(new TimelinePostDataEntity - { - DataTag = tag, - Kind = MimeTypes.TextPlain, - Index = 0, - PostId = postEntity.Id, - LastUpdated = postEntity.LastUpdated - }); - } - else - { - var data = await _dataManager.GetEntryAndCheck(postEntity.Content, "Old image content does not have corresponding data with the tag."); - var format = Image.DetectFormat(data); - database.TimelinePostData.Add(new TimelinePostDataEntity - { - DataTag = postEntity.Content, - Kind = format.DefaultMimeType, - Index = 0, - PostId = postEntity.Id, - LastUpdated = postEntity.LastUpdated - }); - } - } - postEntity.Content = null; - postEntity.ContentType = null; - postEntity.ExtraContent = null; - } - - await database.SaveChangesAsync(); -#pragma warning restore CS0618 - } - } -} diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs index 4f392b2d..932e03c3 100644 --- a/BackEnd/Timeline/Startup.cs +++ b/BackEnd/Timeline/Startup.cs @@ -21,7 +21,7 @@ using Timeline.Models.Converters; using Timeline.Models.Mapper; using Timeline.Routes; using Timeline.Services; -using Timeline.Services.Migration; +using Timeline.Services.DatabaseManagement; using Timeline.Swagger; namespace Timeline @@ -84,12 +84,12 @@ namespace Timeline services.AddAuthorization(); services.AddSingleton(); + // TODO: Remove this. services.TryAddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddCustomMigration(); + services.AddDatabaseManagementService(); services.AddAutoMapper(GetType().Assembly); services.AddMappers(); -- cgit v1.2.3