mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-06 22:36:27 -04:00
Update packages, add dynamic service start/stop logic WIP (#113)
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.6"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.6.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.6.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.0.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -16,7 +16,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("/")]
|
||||
public class RootController : ControllerBase
|
||||
public class RootController(AliasServerDbContext context) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.7" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@@ -41,4 +41,8 @@
|
||||
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Utilities\AliasVault.WorkerStatus\AliasVault.WorkerStatus.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
public class AliasServerDbContext : DbContext
|
||||
public class AliasServerDbContext : WorkerStatusDbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AliasServerDbContext"/> class.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
565
src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.Designer.cs
generated
Normal file
565
src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.Designer.cs
generated
Normal file
@@ -0,0 +1,565 @@
|
||||
// <auto-generated />
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AdminRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AdminUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("LastPasswordChanged")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AdminUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AliasVaultRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AliasVaultRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AliasVaultUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Salt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Verifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AliasVaultUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceIdentifier")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ExpireDate")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AliasVaultUserRefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.Email", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateSystem")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("From")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FromDomain")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FromLocal")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MessageHtml")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MessagePlain")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MessagePreview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MessageSource")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PushNotificationSent")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("To")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ToDomain")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ToLocal")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("Bytes")
|
||||
.IsRequired()
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("EmailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Filesize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EmailId");
|
||||
|
||||
b.ToTable("EmailAttachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.Log", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Application")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Exception")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Level")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LogEvent")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("LogEvent");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MessageTemplate")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Properties")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("TimeStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Application");
|
||||
|
||||
b.HasIndex("TimeStamp");
|
||||
|
||||
b.ToTable("Logs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasServerDb.Vault", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("VaultBlob")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Vaults");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AliasVault.WorkerStatus.WorkerServiceStatus", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("CurrentStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DesiredStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("Heartbeat")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ServiceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("varchar");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("WorkerServiceStatuses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("UserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.ToTable("UserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.ToTable("UserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AliasServerDb.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class WorkerStatusTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WorkerServiceStatuses",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ServiceName = table.Column<string>(type: "varchar", maxLength: 255, nullable: false),
|
||||
CurrentStatus = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
DesiredStatus = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
Heartbeat = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WorkerServiceStatuses", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "WorkerServiceStatuses");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("CurrentStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DesiredStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("Heartbeat")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ServiceName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("varchar");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("WorkerServiceStatuses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
|
||||
<PackageReference Include="MimeKit" Version="4.7.1" />
|
||||
<PackageReference Include="NUglify" Version="1.21.9" />
|
||||
<PackageReference Include="SmtpServer" Version="10.0.1" />
|
||||
|
||||
@@ -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>();
|
||||
// -----------------------------------------------------------------------
|
||||
// Worker status service registration.
|
||||
// -----------------------------------------------------------------------
|
||||
var globalServiceStatus = new GlobalServiceStatus();
|
||||
builder.Services.AddSingleton(globalServiceStatus);
|
||||
|
||||
builder.Services.AddSingleton<Func<IWorkerStatusDbContext>>(sp =>
|
||||
{
|
||||
var factory = sp.GetRequiredService<IDbContextFactory<AliasServerDbContext>>();
|
||||
return () => factory.CreateDbContext();
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton(sp => new WorkerStatusConfiguration
|
||||
{
|
||||
ServiceName = Assembly.GetExecutingAssembly().GetName().Name!,
|
||||
GlobalServiceStatus = sp.GetRequiredService<GlobalServiceStatus>(),
|
||||
});
|
||||
|
||||
builder.Services.AddHostedService<StatusWorker>();
|
||||
|
||||
// Register the names of the various worker services here so their status can be monitored.
|
||||
globalServiceStatus.RegisterWorker(nameof(SmtpServerWorker));
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
builder.Services.AddHostedService<SmtpServerWorker>();
|
||||
|
||||
|
||||
var host = builder.Build();
|
||||
|
||||
using (var scope = host.Services.CreateScope())
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="Worker.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.SmtpService;
|
||||
|
||||
public class Worker(ILogger<Worker> logger, SmtpServer.SmtpServer smtpServer) : BackgroundService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
112
src/Services/AliasVault.SmtpService/Workers/SmtpServerWorker.cs
Normal file
112
src/Services/AliasVault.SmtpService/Workers/SmtpServerWorker.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="SmtpServerWorker.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.SmtpService.Workers;
|
||||
|
||||
using AliasVault.WorkerStatus;
|
||||
|
||||
public class SmtpServerWorker(ILogger<SmtpServerWorker> logger, GlobalServiceStatus globalServiceStatus, SmtpServer.SmtpServer smtpServer) : BackgroundService
|
||||
{
|
||||
private Task? _workerTask;
|
||||
private readonly object _taskLock = new object();
|
||||
|
||||
/// <inheritdoc />
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actual worker logic.
|
||||
/// </summary>
|
||||
/// <param name="stoppingToken"></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,15 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Playwright.NUnit" Version="1.44.0" />
|
||||
<PackageReference Include="Microsoft.Playwright.NUnit" Version="1.45.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="NUnit" Version="3.14.0"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Builder class for creating a test host for the SmtpServiceWorker in order to run integration tests against it.
|
||||
/// </summary>
|
||||
public class TestHostBuilder
|
||||
{
|
||||
/// <summary>
|
||||
@@ -98,7 +102,7 @@ public class TestHostBuilder
|
||||
}
|
||||
);
|
||||
|
||||
services.AddHostedService<Worker>();
|
||||
services.AddHostedService<SmtpServerWorker>();
|
||||
|
||||
// Ensure the in-memory database is populated with tables
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.6"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\stylecop.json">
|
||||
<Link>stylecop.json</Link>
|
||||
</AdditionalFiles>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,37 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="IWorkerStatusDbContext.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AliasVault.WorkerStatus.Database;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for the WorkerStatusDbContext. Inherit from this interface to include the WorkerServiceStatus DbSet
|
||||
/// which is used to store the status of worker services.
|
||||
/// </summary>
|
||||
public interface IWorkerStatusDbContext : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the WorkerServiceStatus DbSet.
|
||||
/// </summary>
|
||||
public DbSet<WorkerServiceStatus> WorkerServiceStatuses { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Save changes to the database.
|
||||
/// </summary>
|
||||
/// <returns>Count of records affected.</returns>
|
||||
public int SaveChanges();
|
||||
|
||||
/// <summary>
|
||||
/// Save changes to the database asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">CancellationToken instance.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="WorkerServiceStatus.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace AliasVault.WorkerStatus.Database;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the status of a worker service for monitoring and control.
|
||||
/// </summary>
|
||||
public class WorkerServiceStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the service status.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the service.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(255)]
|
||||
[Column(TypeName = "varchar")]
|
||||
public string ServiceName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current status of the service.
|
||||
/// </summary>
|
||||
[StringLength(50)]
|
||||
public string CurrentStatus { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the desired status of the service.
|
||||
/// </summary>
|
||||
[StringLength(50)]
|
||||
public string DesiredStatus { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last heartbeat timestamp of the service.
|
||||
/// </summary>
|
||||
public DateTime Heartbeat { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="WorkerStatusDbContext.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AliasVault.WorkerStatus.Database;
|
||||
|
||||
/// <summary>
|
||||
/// WorkerStatusDbContext class.
|
||||
/// </summary>
|
||||
public class WorkerStatusDbContext : DbContext, IWorkerStatusDbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WorkerStatusDbContext"/> class.
|
||||
/// </summary>
|
||||
public WorkerStatusDbContext()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WorkerStatusDbContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">DbContextOptions instance.</param>
|
||||
public WorkerStatusDbContext(DbContextOptions options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the WorkerServiceStatus DbSet.
|
||||
/// </summary>
|
||||
public DbSet<WorkerServiceStatus> WorkerServiceStatuses { get; set; }
|
||||
}
|
||||
|
||||
62
src/Utilities/AliasVault.WorkerStatus/GlobalServiceStatus.cs
Normal file
62
src/Utilities/AliasVault.WorkerStatus/GlobalServiceStatus.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="GlobalServiceStatus.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.WorkerStatus;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
/// <summary>
|
||||
/// Global service status class for monitoring and control.
|
||||
/// </summary>
|
||||
public class GlobalServiceStatus
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, bool> _workerStatuses = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status of the service.
|
||||
/// </summary>
|
||||
public string Status { get; set; } = "Stopped";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current status of the service.
|
||||
/// </summary>
|
||||
public string CurrentStatus { get; set; } = "Stopped";
|
||||
|
||||
/// <summary>
|
||||
/// Register a worker with the service.
|
||||
/// </summary>
|
||||
/// <param name="workerName">Name of the worker</param>
|
||||
public void RegisterWorker(string workerName)
|
||||
{
|
||||
_workerStatuses[workerName] = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the status of a worker.
|
||||
/// </summary>
|
||||
/// <param name="workerName">Name of the worker.</param>
|
||||
/// <param name="isRunning">Boolean which indicates if worker is currently running.</param>
|
||||
public void SetWorkerStatus(string workerName, bool isRunning)
|
||||
{
|
||||
if (_workerStatuses.ContainsKey(workerName))
|
||||
{
|
||||
_workerStatuses[workerName] = isRunning;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns boolean indicating if all workers are running.
|
||||
/// </summary>
|
||||
/// <returns>Boolean which indicates if all workers are started.</returns>
|
||||
public bool AreAllWorkersRunning() => _workerStatuses.All(w => w.Value);
|
||||
|
||||
/// <summary>
|
||||
/// Returns boolean indicating if all workers are stopped.
|
||||
/// </summary>
|
||||
/// <returns>Boolean which indicates if all workers are stopped.</returns>
|
||||
public bool AreAllWorkersStopped() => _workerStatuses.All(w => !w.Value);
|
||||
}
|
||||
162
src/Utilities/AliasVault.WorkerStatus/StatusWorker.cs
Normal file
162
src/Utilities/AliasVault.WorkerStatus/StatusWorker.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="StatusWorker.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using AliasVault.WorkerStatus.Database;
|
||||
|
||||
namespace AliasVault.WorkerStatus;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// StatusWorker class for monitoring and controlling the status of the worker services.
|
||||
/// </summary>
|
||||
public class StatusWorker(ILogger<StatusWorker> logger, WorkerStatusConfiguration config, Func<IWorkerStatusDbContext> createDbContext) : BackgroundService
|
||||
{
|
||||
private IWorkerStatusDbContext _dbContext = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Worker service execution method.
|
||||
/// </summary>
|
||||
/// <param name="stoppingToken">CancellationToken.</param>
|
||||
/// <returns>Task.</returns>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status record of the service from database.
|
||||
/// </summary>
|
||||
/// <returns>New current status.</returns>
|
||||
private async Task<WorkerServiceStatus> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the status of the service.
|
||||
/// </summary>
|
||||
/// <param name="statusEntry">The WorkerServiceStatus entry to update.</param>
|
||||
/// <param name="newStatus">The new status.</param>
|
||||
/// <returns>New current status.</returns>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for all workers to start.
|
||||
/// </summary>
|
||||
/// <param name="stoppingToken">CancellationToken.</param>
|
||||
private async Task WaitForAllWorkersToStart(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!config.GlobalServiceStatus.AreAllWorkersRunning())
|
||||
{
|
||||
logger.LogInformation("Waiting for all workers to start...");
|
||||
await Task.Delay(1000, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for all workers to stop.
|
||||
/// </summary>
|
||||
/// <param name="stoppingToken">CancellationToken.</param>
|
||||
private async Task WaitForAllWorkersToStop(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!config.GlobalServiceStatus.AreAllWorkersStopped())
|
||||
{
|
||||
logger.LogInformation("Waiting for all workers to stop...");
|
||||
await Task.Delay(1000, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves status record or creates an initial status record if it does not exist.
|
||||
/// </summary>
|
||||
private async Task<WorkerServiceStatus> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="WorkerStatusConfiguration.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.WorkerStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for the WorkerStatusDbContext.
|
||||
/// </summary>
|
||||
public class WorkerStatusConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the GlobalServiceStatus for the WorkerStatusDbContext.
|
||||
/// </summary>
|
||||
public GlobalServiceStatus GlobalServiceStatus { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ServiceName for the WorkerStatusDbContext.
|
||||
/// </summary>
|
||||
public string ServiceName { get; set; } = null!;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" />
|
||||
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.0" />
|
||||
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
|
||||
<PackageReference Include="srp" Version="1.0.7" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
Reference in New Issue
Block a user