diff --git a/aliasvault.sln b/aliasvault.sln index 4489a6f30..9396197d1 100644 --- a/aliasvault.sln +++ b/aliasvault.sln @@ -49,6 +49,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.Logging", "src\U EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.RazorComponents", "src\Utilities\AliasVault.RazorComponents\AliasVault.RazorComponents.csproj", "{59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.WorkerStatus", "src\Utilities\AliasVault.WorkerStatus\AliasVault.WorkerStatus.csproj", "{951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -127,6 +129,10 @@ Global {59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC}.Release|Any CPU.Build.0 = Release|Any CPU + {951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -146,6 +152,7 @@ Global {857BCD0E-753F-437A-AF75-B995B4D9A5FE} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8} {FF0B0E64-1AE2-415C-A404-0EB78010821A} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8} {59642CEF-D90A-4A6B-AD3F-9C6300D1E3FC} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8} + {951C3DF8-DF22-4B2B-839F-FBA26DDD8ABD} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FEE82475-C009-4762-8113-A6563D9DC49E} diff --git a/src/AliasVault.Admin/AliasVault.Admin.csproj b/src/AliasVault.Admin/AliasVault.Admin.csproj index 219ab7ecc..137e4ff98 100644 --- a/src/AliasVault.Admin/AliasVault.Admin.csproj +++ b/src/AliasVault.Admin/AliasVault.Admin.csproj @@ -19,10 +19,13 @@ - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AliasVault.Api/AliasVault.Api.csproj b/src/AliasVault.Api/AliasVault.Api.csproj index f8db28bba..fde356662 100644 --- a/src/AliasVault.Api/AliasVault.Api.csproj +++ b/src/AliasVault.Api/AliasVault.Api.csproj @@ -20,13 +20,13 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AliasVault.Api/Controllers/RootController.cs b/src/AliasVault.Api/Controllers/RootController.cs index e4ff55738..968c0d8eb 100644 --- a/src/AliasVault.Api/Controllers/RootController.cs +++ b/src/AliasVault.Api/Controllers/RootController.cs @@ -16,7 +16,7 @@ using Microsoft.EntityFrameworkCore; /// [ApiController] [Route("/")] -public class RootController : ControllerBase +public class RootController(AliasServerDbContext context) : ControllerBase { /// /// Root endpoint that returns a 200 OK if the database connection is successful @@ -30,20 +30,17 @@ public class RootController : ControllerBase { try { - using (var dbContext = new AliasServerDbContext()) + var appliedMigrations = context.Database.GetAppliedMigrations(); + var allMigrations = context.Database.GetMigrations(); + + if (allMigrations.Except(appliedMigrations).Any()) { - var appliedMigrations = dbContext.Database.GetAppliedMigrations(); - var allMigrations = dbContext.Database.GetMigrations(); - - if (allMigrations.Except(appliedMigrations).Any()) - { - // There are pending migrations - return StatusCode(500, "There are pending migrations. Please run 'dotnet ef database update' to apply them."); - } - - // Database is up to date - return Ok("OK"); + // There are pending migrations + return StatusCode(500, "There are pending migrations. Please run 'dotnet ef database update' to apply them."); } + + // Database is up to date + return Ok("OK"); } catch { diff --git a/src/AliasVault.Client/AliasVault.Client.csproj b/src/AliasVault.Client/AliasVault.Client.csproj index 6b73051f7..b00ced633 100644 --- a/src/AliasVault.Client/AliasVault.Client.csproj +++ b/src/AliasVault.Client/AliasVault.Client.csproj @@ -48,10 +48,10 @@ - - - - + + + + all diff --git a/src/Databases/AliasClientDb/AliasClientDb.csproj b/src/Databases/AliasClientDb/AliasClientDb.csproj index daaaba977..a557111b0 100644 --- a/src/Databases/AliasClientDb/AliasClientDb.csproj +++ b/src/Databases/AliasClientDb/AliasClientDb.csproj @@ -17,15 +17,15 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + all diff --git a/src/Databases/AliasServerDb/AliasServerDb.csproj b/src/Databases/AliasServerDb/AliasServerDb.csproj index cbce773a5..0b2f7423e 100644 --- a/src/Databases/AliasServerDb/AliasServerDb.csproj +++ b/src/Databases/AliasServerDb/AliasServerDb.csproj @@ -16,17 +16,17 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - + all @@ -41,4 +41,8 @@ + + + + diff --git a/src/Databases/AliasServerDb/AliasServerDbContext.cs b/src/Databases/AliasServerDb/AliasServerDbContext.cs index 040d174bd..ba89850c4 100644 --- a/src/Databases/AliasServerDb/AliasServerDbContext.cs +++ b/src/Databases/AliasServerDb/AliasServerDbContext.cs @@ -7,6 +7,7 @@ namespace AliasServerDb; +using AliasVault.WorkerStatus.Database; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -16,7 +17,7 @@ using Microsoft.Extensions.Configuration; /// we have two separate user objects, one for the admin panel and one for the vault. We manually /// define the Identity tables in the OnModelCreating method. /// -public class AliasServerDbContext : DbContext +public class AliasServerDbContext : WorkerStatusDbContext { /// /// Initializes a new instance of the class. diff --git a/src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.cs b/src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.cs index 2f1a4faa7..4949d21e1 100644 --- a/src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.cs +++ b/src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.cs @@ -1,4 +1,5 @@ -using System; +// +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.cs b/src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.cs index f69eefdf2..0343633dc 100644 --- a/src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.cs +++ b/src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.cs @@ -1,4 +1,5 @@ -using System; +// +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.Designer.cs new file mode 100644 index 000000000..0f219651f --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.Designer.cs @@ -0,0 +1,565 @@ +// +using System; +using AliasServerDb; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace AliasServerDb.Migrations +{ + [DbContext(typeof(AliasServerDbContext))] + [Migration("20240725202058_WorkerStatusTable")] + partial class WorkerStatusTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true); + + modelBuilder.Entity("AliasServerDb.AdminRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AdminRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AdminUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LastPasswordChanged") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AdminUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("Verifier") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeviceIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("ExpireDate") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AliasVaultUserRefreshTokens"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DateSystem") + .HasColumnType("TEXT"); + + b.Property("From") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FromDomain") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FromLocal") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MessageHtml") + .HasColumnType("TEXT"); + + b.Property("MessagePlain") + .HasColumnType("TEXT"); + + b.Property("MessagePreview") + .HasColumnType("TEXT"); + + b.Property("MessageSource") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PushNotificationSent") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("To") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToDomain") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToLocal") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("DateSystem"); + + b.HasIndex("PushNotificationSent"); + + b.HasIndex("ToLocal"); + + b.HasIndex("Visible"); + + b.ToTable("Emails"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bytes") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("EmailId") + .HasColumnType("INTEGER"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Filesize") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EmailId"); + + b.ToTable("EmailAttachments"); + }); + + modelBuilder.Entity("AliasServerDb.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Application") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Exception") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("LogEvent") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("LogEvent"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MessageTemplate") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TimeStamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Application"); + + b.HasIndex("TimeStamp"); + + b.ToTable("Logs", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("VaultBlob") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("AliasVault.WorkerStatus.WorkerServiceStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CurrentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DesiredStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Heartbeat") + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar"); + + b.HasKey("Id"); + + b.ToTable("WorkerServiceStatuses"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.HasOne("AliasServerDb.Email", "Email") + .WithMany("Attachments") + .HasForeignKey("EmailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Email"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Navigation("Attachments"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.cs b/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.cs new file mode 100644 index 000000000..4252d1ed5 --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.cs @@ -0,0 +1,39 @@ +// +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AliasServerDb.Migrations +{ + /// + public partial class WorkerStatusTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "WorkerServiceStatuses", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ServiceName = table.Column(type: "varchar", maxLength: 255, nullable: false), + CurrentStatus = table.Column(type: "TEXT", maxLength: 50, nullable: false), + DesiredStatus = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Heartbeat = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_WorkerServiceStatuses", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WorkerServiceStatuses"); + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs index 5717731c1..b7f4746c5 100644 --- a/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs @@ -16,7 +16,7 @@ namespace AliasServerDb.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "8.0.7") .HasAnnotation("Proxies:ChangeTracking", false) .HasAnnotation("Proxies:CheckEquality", false) .HasAnnotation("Proxies:LazyLoading", true); @@ -399,6 +399,35 @@ namespace AliasServerDb.Migrations b.ToTable("Vaults"); }); + modelBuilder.Entity("AliasVault.WorkerStatus.WorkerServiceStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CurrentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DesiredStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Heartbeat") + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar"); + + b.HasKey("Id"); + + b.ToTable("WorkerServiceStatuses"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.Property("Id") diff --git a/src/Services/AliasVault.SmtpService/AliasVault.SmtpService.csproj b/src/Services/AliasVault.SmtpService/AliasVault.SmtpService.csproj index 1cb996886..634909937 100644 --- a/src/Services/AliasVault.SmtpService/AliasVault.SmtpService.csproj +++ b/src/Services/AliasVault.SmtpService/AliasVault.SmtpService.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Services/AliasVault.SmtpService/Program.cs b/src/Services/AliasVault.SmtpService/Program.cs index 024dff907..c6b5c5eed 100644 --- a/src/Services/AliasVault.SmtpService/Program.cs +++ b/src/Services/AliasVault.SmtpService/Program.cs @@ -6,6 +6,7 @@ //----------------------------------------------------------------------- using System.Data.Common; +using System.Reflection; using System.Security.Cryptography.X509Certificates; using AliasServerDb; using AliasVault.SmtpService; @@ -15,11 +16,14 @@ using Microsoft.EntityFrameworkCore; using SmtpServer; using SmtpServer.Storage; using AliasVault.Logging; +using AliasVault.SmtpService.Workers; +using AliasVault.WorkerStatus; +using AliasVault.WorkerStatus.Database; var builder = Host.CreateApplicationBuilder(args); builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true); -builder.Services.ConfigureLogging(builder.Configuration, "SmtpService"); +builder.Services.ConfigureLogging(builder.Configuration, Assembly.GetExecutingAssembly().GetName().Name!); // Create global config object, get values from environment variables. Config config = new Config(); @@ -118,7 +122,33 @@ builder.Services.AddSingleton( } ); -builder.Services.AddHostedService(); +// ----------------------------------------------------------------------- +// Worker status service registration. +// ----------------------------------------------------------------------- +var globalServiceStatus = new GlobalServiceStatus(); +builder.Services.AddSingleton(globalServiceStatus); + +builder.Services.AddSingleton>(sp => +{ + var factory = sp.GetRequiredService>(); + return () => factory.CreateDbContext(); +}); + +builder.Services.AddSingleton(sp => new WorkerStatusConfiguration +{ + ServiceName = Assembly.GetExecutingAssembly().GetName().Name!, + GlobalServiceStatus = sp.GetRequiredService(), +}); + +builder.Services.AddHostedService(); + +// Register the names of the various worker services here so their status can be monitored. +globalServiceStatus.RegisterWorker(nameof(SmtpServerWorker)); +// ----------------------------------------------------------------------- + +builder.Services.AddHostedService(); + + var host = builder.Build(); using (var scope = host.Services.CreateScope()) diff --git a/src/Services/AliasVault.SmtpService/Worker.cs b/src/Services/AliasVault.SmtpService/Worker.cs deleted file mode 100644 index 2d6f168ca..000000000 --- a/src/Services/AliasVault.SmtpService/Worker.cs +++ /dev/null @@ -1,45 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) lanedirt. All rights reserved. -// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. -// -//----------------------------------------------------------------------- - -namespace AliasVault.SmtpService; - -public class Worker(ILogger logger, SmtpServer.SmtpServer smtpServer) : BackgroundService -{ - /// - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - try - { - logger.LogWarning("AliasVault.SmtpService starting at: {Time}", DateTimeOffset.Now); - - // Start the SMTP server - await smtpServer.StartAsync(stoppingToken); - - // Wait for the cancellation token to be triggered - await Task.Delay(Timeout.Infinite, stoppingToken); - } - catch (OperationCanceledException ex) - { - // This exception is thrown when the stoppingToken is canceled - // It's expected behavior, so we can just log it - logger.LogWarning(ex, "AliasVault.SmtpService is stopping due to a cancellation request."); - } - catch (Exception ex) - { - // Log any unexpected exceptions - logger.LogError(ex, "An error occurred in AliasVault.SmtpService"); - } - finally - { - // Log that the service is stopping, whether it's due to cancellation or an error - logger.LogWarning("AliasVault.SmtpService stopped at: {Time}", DateTimeOffset.Now); - - // Ensure the SMTP server is stopped - smtpServer.Shutdown(); - } - } -} diff --git a/src/Services/AliasVault.SmtpService/Workers/SmtpServerWorker.cs b/src/Services/AliasVault.SmtpService/Workers/SmtpServerWorker.cs new file mode 100644 index 000000000..ab63f2224 --- /dev/null +++ b/src/Services/AliasVault.SmtpService/Workers/SmtpServerWorker.cs @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.SmtpService.Workers; + +using AliasVault.WorkerStatus; + +public class SmtpServerWorker(ILogger logger, GlobalServiceStatus globalServiceStatus, SmtpServer.SmtpServer smtpServer) : BackgroundService +{ + private Task? _workerTask; + private readonly object _taskLock = new object(); + + /// + protected override async Task ExecuteAsync(CancellationToken serviceCancellationToken) + { + logger.LogInformation("AliasVault.SmtpService.SmtpServerWorker ExecuteAsync called."); + + // Create a new cancellation token source for worker. + using var workerCancellationTokenSource = new CancellationTokenSource(); + var workerCancellationToken = workerCancellationTokenSource.Token; + + while (!serviceCancellationToken.IsCancellationRequested) + { + if (globalServiceStatus.CurrentStatus == "Started" || globalServiceStatus.CurrentStatus == "Starting") + { + // Set worker status to true for acknowledgement. + globalServiceStatus.SetWorkerStatus(nameof(SmtpServerWorker), true); + + // Start the worker in the background. + lock (_taskLock) + { + if (_workerTask == null) + { + // Reset the worker cancellation token if it was canceled before + _workerTask = Task.Run(() => WorkerLogic(workerCancellationToken), workerCancellationToken); + } + } + } + else if (globalServiceStatus.CurrentStatus == "Stopping") + { + // Request the worker to stop. + await workerCancellationTokenSource.CancelAsync(); + } + else if (globalServiceStatus.CurrentStatus == "Stopped") + { + // Ensure worker task is completed and reset it so it can be started again. + if (_workerTask != null) + { + try + { + await _workerTask; + } + catch (OperationCanceledException) + { + // Task was cancelled, handle if needed. + } + _workerTask = null; + } + } + + await Task.Delay(1000, serviceCancellationToken); + } + + // If we reach this point, the service is hard stopping: not in software but on OS level. + // Request the actual worker to stop. + await workerCancellationTokenSource.CancelAsync(); + } + + /// + /// Actual worker logic. + /// + /// + private async Task WorkerLogic(CancellationToken stoppingToken) + { + try + { + logger.LogWarning("AliasVault.SmtpService starting at: {Time}", DateTimeOffset.Now); + + // Start the SMTP server + await smtpServer.StartAsync(stoppingToken); + + // Wait for the cancellation token to be triggered + await Task.Delay(Timeout.Infinite, stoppingToken); + } + catch (OperationCanceledException ex) + { + // This exception is thrown when the stoppingToken is canceled + // It's expected behavior, so we can just log it + logger.LogWarning(ex, "AliasVault.SmtpService is stopping due to a cancellation request."); + } + catch (Exception ex) + { + // Log any unexpected exceptions + logger.LogError(ex, "An error occurred in AliasVault.SmtpService"); + } + finally + { + // Log that the service is stopping, whether it's due to cancellation or an error + logger.LogWarning("AliasVault.SmtpService stopped at: {Time}", DateTimeOffset.Now); + + // Ensure the SMTP server is stopped + smtpServer.Shutdown(); + + // Set worker status to false for acknowledgement. + globalServiceStatus.SetWorkerStatus(nameof(SmtpServerWorker), false); + } + } +} diff --git a/src/Tests/AliasVault.E2ETests/AliasVault.E2ETests.csproj b/src/Tests/AliasVault.E2ETests/AliasVault.E2ETests.csproj index e8a408ac1..73d291ae4 100644 --- a/src/Tests/AliasVault.E2ETests/AliasVault.E2ETests.csproj +++ b/src/Tests/AliasVault.E2ETests/AliasVault.E2ETests.csproj @@ -24,15 +24,15 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Tests/AliasVault.IntegrationTests/AliasVault.IntegrationTests.csproj b/src/Tests/AliasVault.IntegrationTests/AliasVault.IntegrationTests.csproj index 3c5af2523..939ba9b08 100644 --- a/src/Tests/AliasVault.IntegrationTests/AliasVault.IntegrationTests.csproj +++ b/src/Tests/AliasVault.IntegrationTests/AliasVault.IntegrationTests.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs b/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs index c52e6c004..853bf414c 100644 --- a/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs +++ b/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs @@ -6,6 +6,7 @@ // ----------------------------------------------------------------------- using AliasVault.SmtpService.Handlers; +using AliasVault.SmtpService.Workers; namespace AliasVault.IntegrationTests.SmtpServer; @@ -19,6 +20,9 @@ using Microsoft.EntityFrameworkCore; using global::SmtpServer; using global::SmtpServer.Storage; +/// +/// Builder class for creating a test host for the SmtpServiceWorker in order to run integration tests against it. +/// public class TestHostBuilder { /// @@ -98,7 +102,7 @@ public class TestHostBuilder } ); - services.AddHostedService(); + services.AddHostedService(); // Ensure the in-memory database is populated with tables var serviceProvider = services.BuildServiceProvider(); diff --git a/src/Tests/AliasVault.UnitTests/AliasVault.UnitTests.csproj b/src/Tests/AliasVault.UnitTests/AliasVault.UnitTests.csproj index 3f6a682f6..14e1ed894 100644 --- a/src/Tests/AliasVault.UnitTests/AliasVault.UnitTests.csproj +++ b/src/Tests/AliasVault.UnitTests/AliasVault.UnitTests.csproj @@ -33,7 +33,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Tests/Server/AliasVault.E2ETests.Client.Server/AliasVault.E2ETests.Client.Server.csproj b/src/Tests/Server/AliasVault.E2ETests.Client.Server/AliasVault.E2ETests.Client.Server.csproj index 1ce08073d..422df3738 100644 --- a/src/Tests/Server/AliasVault.E2ETests.Client.Server/AliasVault.E2ETests.Client.Server.csproj +++ b/src/Tests/Server/AliasVault.E2ETests.Client.Server/AliasVault.E2ETests.Client.Server.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Utilities/AliasVault.Logging/AliasVault.Logging.csproj b/src/Utilities/AliasVault.Logging/AliasVault.Logging.csproj index c58d344b9..2604c1776 100644 --- a/src/Utilities/AliasVault.Logging/AliasVault.Logging.csproj +++ b/src/Utilities/AliasVault.Logging/AliasVault.Logging.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Utilities/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj b/src/Utilities/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj index 3e110ed9f..d5a1d414c 100644 --- a/src/Utilities/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj +++ b/src/Utilities/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Utilities/AliasVault.WorkerStatus/AliasVault.WorkerStatus.csproj b/src/Utilities/AliasVault.WorkerStatus/AliasVault.WorkerStatus.csproj new file mode 100644 index 000000000..888528002 --- /dev/null +++ b/src/Utilities/AliasVault.WorkerStatus/AliasVault.WorkerStatus.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + stylecop.json + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/Utilities/AliasVault.WorkerStatus/Database/IWorkerStatusDbContext.cs b/src/Utilities/AliasVault.WorkerStatus/Database/IWorkerStatusDbContext.cs new file mode 100644 index 000000000..57b0d9670 --- /dev/null +++ b/src/Utilities/AliasVault.WorkerStatus/Database/IWorkerStatusDbContext.cs @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +using Microsoft.EntityFrameworkCore; + +namespace AliasVault.WorkerStatus.Database; + +/// +/// Interface for the WorkerStatusDbContext. Inherit from this interface to include the WorkerServiceStatus DbSet +/// which is used to store the status of worker services. +/// +public interface IWorkerStatusDbContext : IDisposable +{ + /// + /// Gets or sets the WorkerServiceStatus DbSet. + /// + public DbSet WorkerServiceStatuses { get; set; } + + /// + /// Save changes to the database. + /// + /// Count of records affected. + public int SaveChanges(); + + /// + /// Save changes to the database asynchronously. + /// + /// CancellationToken instance. + /// Task. + public Task SaveChangesAsync(CancellationToken cancellationToken = default); +} + + diff --git a/src/Utilities/AliasVault.WorkerStatus/Database/WorkerServiceStatus.cs b/src/Utilities/AliasVault.WorkerStatus/Database/WorkerServiceStatus.cs new file mode 100644 index 000000000..bf10c7c00 --- /dev/null +++ b/src/Utilities/AliasVault.WorkerStatus/Database/WorkerServiceStatus.cs @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace AliasVault.WorkerStatus.Database; + +/// +/// Represents the status of a worker service for monitoring and control. +/// +public class WorkerServiceStatus +{ + /// + /// Gets or sets the unique identifier for the service status. + /// + public int Id { get; set; } + + /// + /// Gets or sets the name of the service. + /// + [Required] + [StringLength(255)] + [Column(TypeName = "varchar")] + public string ServiceName { get; set; } = null!; + + /// + /// Gets or sets the current status of the service. + /// + [StringLength(50)] + public string CurrentStatus { get; set; } = null!; + + /// + /// Gets or sets the desired status of the service. + /// + [StringLength(50)] + public string DesiredStatus { get; set; } = null!; + + /// + /// Gets or sets the last heartbeat timestamp of the service. + /// + public DateTime Heartbeat { get; set; } +} diff --git a/src/Utilities/AliasVault.WorkerStatus/Database/WorkerStatusDbContext.cs b/src/Utilities/AliasVault.WorkerStatus/Database/WorkerStatusDbContext.cs new file mode 100644 index 000000000..74852c9f5 --- /dev/null +++ b/src/Utilities/AliasVault.WorkerStatus/Database/WorkerStatusDbContext.cs @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +using Microsoft.EntityFrameworkCore; + +namespace AliasVault.WorkerStatus.Database; + +/// +/// WorkerStatusDbContext class. +/// +public class WorkerStatusDbContext : DbContext, IWorkerStatusDbContext +{ + /// + /// Initializes a new instance of the class. + /// + public WorkerStatusDbContext() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// DbContextOptions instance. + public WorkerStatusDbContext(DbContextOptions options) + : base(options) + { + } + + /// + /// Gets or sets the WorkerServiceStatus DbSet. + /// + public DbSet WorkerServiceStatuses { get; set; } +} + diff --git a/src/Utilities/AliasVault.WorkerStatus/GlobalServiceStatus.cs b/src/Utilities/AliasVault.WorkerStatus/GlobalServiceStatus.cs new file mode 100644 index 000000000..3fb04b6bf --- /dev/null +++ b/src/Utilities/AliasVault.WorkerStatus/GlobalServiceStatus.cs @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.WorkerStatus; + +using System.Collections.Concurrent; + +/// +/// Global service status class for monitoring and control. +/// +public class GlobalServiceStatus +{ + private readonly ConcurrentDictionary _workerStatuses = new(); + + /// + /// Gets or sets the status of the service. + /// + public string Status { get; set; } = "Stopped"; + + /// + /// Gets or sets the current status of the service. + /// + public string CurrentStatus { get; set; } = "Stopped"; + + /// + /// Register a worker with the service. + /// + /// Name of the worker + public void RegisterWorker(string workerName) + { + _workerStatuses[workerName] = false; + } + + /// + /// Set the status of a worker. + /// + /// Name of the worker. + /// Boolean which indicates if worker is currently running. + public void SetWorkerStatus(string workerName, bool isRunning) + { + if (_workerStatuses.ContainsKey(workerName)) + { + _workerStatuses[workerName] = isRunning; + } + } + + /// + /// Returns boolean indicating if all workers are running. + /// + /// Boolean which indicates if all workers are started. + public bool AreAllWorkersRunning() => _workerStatuses.All(w => w.Value); + + /// + /// Returns boolean indicating if all workers are stopped. + /// + /// Boolean which indicates if all workers are stopped. + public bool AreAllWorkersStopped() => _workerStatuses.All(w => !w.Value); +} diff --git a/src/Utilities/AliasVault.WorkerStatus/StatusWorker.cs b/src/Utilities/AliasVault.WorkerStatus/StatusWorker.cs new file mode 100644 index 000000000..265472b68 --- /dev/null +++ b/src/Utilities/AliasVault.WorkerStatus/StatusWorker.cs @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +using AliasVault.WorkerStatus.Database; + +namespace AliasVault.WorkerStatus; + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +/// +/// StatusWorker class for monitoring and controlling the status of the worker services. +/// +public class StatusWorker(ILogger logger, WorkerStatusConfiguration config, Func createDbContext) : BackgroundService +{ + private IWorkerStatusDbContext _dbContext = null!; + + /// + /// Worker service execution method. + /// + /// CancellationToken. + /// Task. + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + _dbContext = createDbContext(); + + try + { + var statusEntry = await GetServiceStatus(); + switch (statusEntry.CurrentStatus) + { + case "Starting": + await WaitForAllWorkersToStart(stoppingToken); + await SetServiceStatus(statusEntry, "Started"); + logger.LogInformation("All workers started."); + break; + case "Stopping": + await WaitForAllWorkersToStop(stoppingToken); + await SetServiceStatus(statusEntry, "Stopped"); + logger.LogInformation("All workers stopped."); + break; + case "Stopped": + logger.LogInformation("Service is (soft) stopped."); + break; + } + } + catch (Exception e) + { + logger.LogError(e, "Global main application exception"); + } + + await Task.Delay(5000, stoppingToken); + } + + // If we reach this point, the service is hard stopping: not in software but on OS level. + // Mark the service as stopped. + _dbContext = createDbContext(); + await SetServiceStatus(await GetServiceStatus(), "Stopped"); + } + + /// + /// Gets the current status record of the service from database. + /// + /// New current status. + private async Task GetServiceStatus() + { + var entry = await GetOrCreateInitialStatusRecord(); + + if (!string.IsNullOrEmpty(entry.DesiredStatus) && entry.CurrentStatus != entry.DesiredStatus) + { + entry.CurrentStatus = entry.DesiredStatus switch + { + "Started" => "Starting", + "Stopped" => "Stopping", + _ => entry.CurrentStatus, + }; + } + + config.GlobalServiceStatus.Status = entry.CurrentStatus; + config.GlobalServiceStatus.CurrentStatus = entry.CurrentStatus; + + entry.Heartbeat = DateTime.Now; + await _dbContext.SaveChangesAsync(); + + return entry; + } + + /// + /// Updates the status of the service. + /// + /// The WorkerServiceStatus entry to update. + /// The new status. + /// New current status. + private async Task SetServiceStatus(WorkerServiceStatus statusEntry, string newStatus = "") + { + if (!string.IsNullOrEmpty(newStatus) && statusEntry.CurrentStatus != newStatus) + { + statusEntry.CurrentStatus = newStatus; + } + + var status = statusEntry.CurrentStatus; + config.GlobalServiceStatus.Status = status; + config.GlobalServiceStatus.CurrentStatus = status; + + statusEntry.Heartbeat = DateTime.Now; + await _dbContext.SaveChangesAsync(); + } + + /// + /// Waits for all workers to start. + /// + /// CancellationToken. + private async Task WaitForAllWorkersToStart(CancellationToken stoppingToken) + { + while (!config.GlobalServiceStatus.AreAllWorkersRunning()) + { + logger.LogInformation("Waiting for all workers to start..."); + await Task.Delay(1000, stoppingToken); + } + } + + /// + /// Waits for all workers to stop. + /// + /// CancellationToken. + private async Task WaitForAllWorkersToStop(CancellationToken stoppingToken) + { + while (!config.GlobalServiceStatus.AreAllWorkersStopped()) + { + logger.LogInformation("Waiting for all workers to stop..."); + await Task.Delay(1000, stoppingToken); + } + } + + /// + /// Retrieves status record or creates an initial status record if it does not exist. + /// + private async Task GetOrCreateInitialStatusRecord() + { + var entry = _dbContext.WorkerServiceStatuses.FirstOrDefault(x => x.ServiceName == config.ServiceName); + if (entry != null) + { + return entry; + } + + entry = new WorkerServiceStatus + { + ServiceName = config.ServiceName, + CurrentStatus = "Started", + DesiredStatus = string.Empty, + }; + await _dbContext.WorkerServiceStatuses.AddAsync(entry); + + return entry; + } +} diff --git a/src/Utilities/AliasVault.WorkerStatus/WorkerStatusConfiguration.cs b/src/Utilities/AliasVault.WorkerStatus/WorkerStatusConfiguration.cs new file mode 100644 index 000000000..91a59e568 --- /dev/null +++ b/src/Utilities/AliasVault.WorkerStatus/WorkerStatusConfiguration.cs @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.WorkerStatus; + +/// +/// Interface for the WorkerStatusDbContext. +/// +public class WorkerStatusConfiguration +{ + /// + /// Gets or sets the GlobalServiceStatus for the WorkerStatusDbContext. + /// + public GlobalServiceStatus GlobalServiceStatus { get; set; } = null!; + + /// + /// Gets or sets the ServiceName for the WorkerStatusDbContext. + /// + public string ServiceName { get; set; } = null!; +} + + diff --git a/src/Utilities/Cryptography/Cryptography.csproj b/src/Utilities/Cryptography/Cryptography.csproj index c25f54394..4466c730b 100644 --- a/src/Utilities/Cryptography/Cryptography.csproj +++ b/src/Utilities/Cryptography/Cryptography.csproj @@ -22,7 +22,7 @@ - + all