From 822b95d9406ebb6f1968ec632ba5ca032aa8e1f0 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Jun 2025 15:29:14 +0200 Subject: [PATCH] Refactor vault sql to include release info (#955) --- apps/server/AliasVault.Client/GlobalUsings.cs | 1 + .../Main/Components/Email/RecentEmails.razor | 1 - .../Main/Pages/Credentials/AddEdit.razor | 7 +- .../StatusMessages/PendingMigrations.razor | 11 +- .../Services/Database/DbService.cs | 56 +++-- .../{ => JsInterop}/JsInteropService.cs | 210 ++++-------------- .../JsInterop/Models/AliasVaultIdentity.cs | 44 ++++ .../JsInterop/Models/SqlGenerationResult.cs | 39 ++++ .../JsInterop/Models/SqlVaultVersion.cs | 39 ++++ apps/server/AliasVault.Client/_Imports.razor | 1 + .../js/dist/shared/vault-sql/index.d.mts | 17 +- .../js/dist/shared/vault-sql/index.d.ts | 17 +- .../wwwroot/js/dist/shared/vault-sql/index.js | 31 ++- .../js/dist/shared/vault-sql/index.mjs | 31 ++- .../Scripts/convert-sql-to-ts.sh | 60 ----- .../AliasClientDb/Scripts/run-all.sh | 4 +- docs/misc/dev/upgrade-ef-client-model.md | 20 +- .../src/__tests__/VaultSqlGenerator.test.ts | 53 ++--- shared/vault-sql/src/sql/VaultSqlGenerator.ts | 6 +- shared/vault-sql/src/sql/VaultVersions.ts | 31 ++- shared/vault-sql/src/types/VaultVersion.ts | 10 +- 21 files changed, 355 insertions(+), 334 deletions(-) rename apps/server/AliasVault.Client/Services/{ => JsInterop}/JsInteropService.cs (82%) create mode 100644 apps/server/AliasVault.Client/Services/JsInterop/Models/AliasVaultIdentity.cs create mode 100644 apps/server/AliasVault.Client/Services/JsInterop/Models/SqlGenerationResult.cs create mode 100644 apps/server/AliasVault.Client/Services/JsInterop/Models/SqlVaultVersion.cs diff --git a/apps/server/AliasVault.Client/GlobalUsings.cs b/apps/server/AliasVault.Client/GlobalUsings.cs index b25c52885..d80a3764c 100644 --- a/apps/server/AliasVault.Client/GlobalUsings.cs +++ b/apps/server/AliasVault.Client/GlobalUsings.cs @@ -12,4 +12,5 @@ global using AliasVault.Client.Main.Models; global using AliasVault.Client.Services; global using AliasVault.Client.Services.Auth; +global using AliasVault.Client.Services.JsInterop; global using AliasVault.Client.Services.Database; diff --git a/apps/server/AliasVault.Client/Main/Components/Email/RecentEmails.razor b/apps/server/AliasVault.Client/Main/Components/Email/RecentEmails.razor index 3289bcf94..989e0a69d 100644 --- a/apps/server/AliasVault.Client/Main/Components/Email/RecentEmails.razor +++ b/apps/server/AliasVault.Client/Main/Components/Email/RecentEmails.razor @@ -8,7 +8,6 @@ @inject HttpClient HttpClient @inject JsInteropService JsInteropService @inject DbService DbService -@inject Config Config @inject EmailService EmailService @using AliasVault.Shared.Core @inject ILogger Logger diff --git a/apps/server/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor b/apps/server/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor index c7a13595c..505788db3 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Credentials/AddEdit.razor @@ -4,6 +4,7 @@ @inject CredentialService CredentialService @inject IJSRuntime JSRuntime @inject AliasVault.Client.Services.QuickCreateStateService QuickCreateStateService +@using AliasVault.Client.Services.JsInterop.Models @implements IAsyncDisposable @@ -14,11 +15,11 @@

Your vault: - @CurrentVersion + @(CurrentVersion?.ReleaseVersion ?? "...")

AliasVault latest version: - @LatestVersion + @(LatestVersion?.ReleaseVersion ?? "...")

@@ -47,8 +48,8 @@ @code { private bool IsPendingMigrations { get; set; } = false; private string ErrorMessage { get; set; } = string.Empty; - private string CurrentVersion { get; set; } = string.Empty; - private string LatestVersion { get; set; } = string.Empty; + private SqlVaultVersion? CurrentVersion { get; set; } + private SqlVaultVersion? LatestVersion { get; set; } /// protected override async Task OnInitializedAsync() diff --git a/apps/server/AliasVault.Client/Services/Database/DbService.cs b/apps/server/AliasVault.Client/Services/Database/DbService.cs index 573c899e0..e8a3caefc 100644 --- a/apps/server/AliasVault.Client/Services/Database/DbService.cs +++ b/apps/server/AliasVault.Client/Services/Database/DbService.cs @@ -10,7 +10,9 @@ namespace AliasVault.Client.Services.Database; using System.Data; using System.Net.Http.Json; using AliasClientDb; +using AliasVault.Client.Services; using AliasVault.Client.Services.Auth; +using AliasVault.Client.Services.JsInterop.Models; using AliasVault.Shared.Models.Enums; using AliasVault.Shared.Models.WebApi.Vault; using Microsoft.Data.Sqlite; @@ -346,9 +348,15 @@ public sealed class DbService : IDisposable { try { + // Get current version of database. + var currentVersion = await GetCurrentDatabaseVersionAsync(); + + // Get latest version from JsInteropService. + var latestVersion = await _jsInteropService.GetLatestVaultVersionAsync(); + // TODO: migrate database to the latest version via JS interop... // Call JS interop to get SQL commands to create a new vault with the latest schema. - var sqlCommands = await _jsInteropService.GetCreateVaultSqlAsync(); + var sqlCommands = await _jsInteropService.GetUpgradeVaultSqlAsync(currentVersion.Revision, latestVersion.Revision); // Execute the SQL commands to create a new vault with the latest schema. foreach (var sqlCommand in sqlCommands.SqlCommands) @@ -372,10 +380,11 @@ public sealed class DbService : IDisposable /// Get the current version (applied migration) of the database that is loaded in memory. /// /// Version as string. - public async Task GetCurrentDatabaseVersionAsync() + public async Task GetCurrentDatabaseVersionAsync() { var migrations = await _dbContext.Database.GetAppliedMigrationsAsync(); var lastMigration = migrations.LastOrDefault(); + var currentVersion = "Unknown"; // Convert migration Id in the form of "20240708094944_1.0.0-InitialMigration" to "1.0.0". if (lastMigration is not null) @@ -386,38 +395,41 @@ public sealed class DbService : IDisposable var versionPart = parts[1].Split('-')[0]; if (Version.TryParse(versionPart, out _)) { - return versionPart; + currentVersion = versionPart; } } } - return "Unknown"; + // Get all available vault versions to get the revision number of the current version. + var allVersions = await _jsInteropService.GetAllVaultVersionsAsync(); + var currentVersionRevision = allVersions.FirstOrDefault(v => v.Version == currentVersion); + + return currentVersionRevision ?? new SqlVaultVersion + { + Revision = 0, + Version = "Unknown", + Description = "Unknown", + ReleaseDate = DateTime.MinValue, + ReleaseVersion = "Unknown", + }; } /// /// Get the latest available version (EF migration) as defined in code. /// /// Version as string. - public async Task GetLatestDatabaseVersionAsync() + public async Task GetLatestDatabaseVersionAsync() { - var migrations = await _dbContext.Database.GetPendingMigrationsAsync(); - var lastMigration = migrations.LastOrDefault(); + var allVersions = await _jsInteropService.GetAllVaultVersionsAsync(); + var latestVersion = allVersions.LastOrDefault(); - // Convert migration Id in the form of "20240708094944_1.0.0-InitialMigration" to "1.0.0". - if (lastMigration is not null) + return latestVersion ?? new SqlVaultVersion { - var parts = lastMigration.Split('_'); - if (parts.Length > 1) - { - var versionPart = parts[1].Split('-')[0]; - if (Version.TryParse(versionPart, out _)) - { - return versionPart; - } - } - } - - return "Unknown"; + Revision = 0, + Version = "Unknown", + Description = "Unknown", + ReleaseDate = DateTime.MinValue, + }; } /// @@ -436,7 +448,7 @@ public sealed class DbService : IDisposable { Username = username, Blob = encryptedDatabase, - Version = databaseVersion, + Version = databaseVersion.Version, CurrentRevisionNumber = _vaultRevisionNumber, EncryptionPublicKey = encryptionKey.PublicKey, CredentialsCount = credentialsCount, diff --git a/apps/server/AliasVault.Client/Services/JsInteropService.cs b/apps/server/AliasVault.Client/Services/JsInterop/JsInteropService.cs similarity index 82% rename from apps/server/AliasVault.Client/Services/JsInteropService.cs rename to apps/server/AliasVault.Client/Services/JsInterop/JsInteropService.cs index a9416f6be..44fb11a23 100644 --- a/apps/server/AliasVault.Client/Services/JsInteropService.cs +++ b/apps/server/AliasVault.Client/Services/JsInterop/JsInteropService.cs @@ -5,10 +5,11 @@ // //----------------------------------------------------------------------- -namespace AliasVault.Client.Services; +namespace AliasVault.Client.Services.JsInterop; using System.Security.Cryptography; using System.Text.Json; +using AliasVault.Client.Services.JsInterop.Models; using Microsoft.JSInterop; /// @@ -454,34 +455,58 @@ public sealed class JsInteropService(IJSRuntime jsRuntime) } } + /// + /// Gets all available vault versions. + /// + /// List of vault versions. + public async Task> GetAllVaultVersionsAsync() + { + if (_vaultSqlInteropModule == null) + { + await InitializeAsync(); + if (_vaultSqlInteropModule == null) + { + throw new InvalidOperationException("Failed to initialize identity generator module"); + } + } + + var vaultGenerator = await _vaultSqlInteropModule.InvokeAsync("CreateVaultSqlGenerator"); + var result = await vaultGenerator.InvokeAsync("getAllVersions"); + return result.EnumerateArray().Select(x => new SqlVaultVersion + { + Revision = x.GetProperty("revision").GetInt32(), + Version = x.GetProperty("version").GetString() ?? string.Empty, + Description = x.GetProperty("description").GetString() ?? string.Empty, + ReleaseDate = DateTime.Parse(x.GetProperty("releaseDate").GetString() ?? string.Empty), + ReleaseVersion = x.GetProperty("releaseVersion").GetString() ?? string.Empty, + }).ToList(); + } + /// /// Gets SQL commands to check current vault version. /// /// Array of SQL commands to execute. - public async Task GetVersionCheckSqlAsync() + public async Task GetLatestVaultVersionAsync() { - try + if (_vaultSqlInteropModule == null) { + await InitializeAsync(); if (_vaultSqlInteropModule == null) { - await InitializeAsync(); - if (_vaultSqlInteropModule == null) - { - throw new InvalidOperationException("Failed to initialize identity generator module"); - } + throw new InvalidOperationException("Failed to initialize identity generator module"); } + } - var vaultGenerator = await _vaultSqlInteropModule.InvokeAsync("CreateVaultSqlGenerator"); - var result = await vaultGenerator.InvokeAsync("getVersionCheckSql"); - return result.EnumerateArray() - .Select(x => x.GetString() ?? string.Empty) - .Where(x => !string.IsNullOrEmpty(x)) - .ToArray(); - } - catch (JSException) + var vaultGenerator = await _vaultSqlInteropModule.InvokeAsync("CreateVaultSqlGenerator"); + var result = await vaultGenerator.InvokeAsync("getLatestVersion"); + return new SqlVaultVersion { - return []; - } + Revision = result.GetProperty("revision").GetInt32(), + Version = result.GetProperty("version").GetString() ?? string.Empty, + Description = result.GetProperty("description").GetString() ?? string.Empty, + ReleaseDate = DateTime.Parse(result.GetProperty("releaseDate").GetString() ?? string.Empty), + ReleaseVersion = result.GetProperty("releaseVersion").GetString() ?? string.Empty, + }; } /// @@ -558,52 +583,6 @@ public sealed class JsInteropService(IJSRuntime jsRuntime) } } - /// - /// Parses vault version information from query results. - /// - /// Whether Settings table exists. - /// Version query result. - /// Migration number query result. - /// Parsed vault version information. - public async Task ParseVaultVersionInfoAsync(bool settingsTableExists, string? versionResult = null, string? migrationResult = null) - { - try - { - if (_vaultSqlInteropModule == null) - { - await InitializeAsync(); - if (_vaultSqlInteropModule == null) - { - throw new InvalidOperationException("Failed to initialize identity generator module"); - } - } - - var vaultGenerator = await _vaultSqlInteropModule.InvokeAsync("CreateVaultSqlGenerator"); - var result = await vaultGenerator.InvokeAsync("parseVaultVersionInfo", settingsTableExists, versionResult, migrationResult); - - return new VaultVersionInfo - { - CurrentVersion = result.GetProperty("currentVersion").GetString() ?? "0.0.0", - CurrentMigrationNumber = result.GetProperty("currentMigrationNumber").GetInt32(), - TargetVersion = result.GetProperty("targetVersion").GetString() ?? "0.0.0", - TargetMigrationNumber = result.GetProperty("targetMigrationNumber").GetInt32(), - NeedsUpgrade = result.GetProperty("needsUpgrade").GetBoolean(), - }; - } - catch (JSException ex) - { - return new VaultVersionInfo - { - CurrentVersion = "0.0.0", - CurrentMigrationNumber = 0, - TargetVersion = "0.0.0", - TargetMigrationNumber = 0, - NeedsUpgrade = true, - Error = $"JavaScript error: {ex.Message}", - }; - } - } - /// /// Validates vault structure from table names. /// @@ -631,109 +610,6 @@ public sealed class JsInteropService(IJSRuntime jsRuntime) } } - /// - /// Represents the result of a JavaScript identity generator operation. - /// - public sealed class AliasVaultIdentity - { - /// - /// Gets the first name. - /// - public string? FirstName { get; init; } - - /// - /// Gets the last name. - /// - public string? LastName { get; init; } - - /// - /// Gets the birth date. - /// - public string? BirthDate { get; init; } - - /// - /// Gets the email prefix. - /// - public string? EmailPrefix { get; init; } - - /// - /// Gets the nickname. - /// - public string? NickName { get; init; } - - /// - /// Gets the gender. - /// - public string? Gender { get; init; } - } - - /// - /// Represents the result of SQL generation for vault operations. - /// - public sealed class SqlGenerationResult - { - /// - /// Gets a value indicating whether the SQL generation was successful. - /// - public bool Success { get; init; } - - /// - /// Gets the generated SQL commands to execute. - /// - public List SqlCommands { get; init; } = []; - - /// - /// Gets the vault version. - /// - public string Version { get; init; } = "0.0.0"; - - /// - /// Gets the migration number. - /// - public int MigrationNumber { get; init; } - - /// - /// Gets the optional error message. - /// - public string? Error { get; init; } - } - - /// - /// Represents vault version information. - /// - public sealed class VaultVersionInfo - { - /// - /// Gets the current vault version. - /// - public string CurrentVersion { get; init; } = "0.0.0"; - - /// - /// Gets the current migration number. - /// - public int CurrentMigrationNumber { get; init; } - - /// - /// Gets the target vault version. - /// - public string TargetVersion { get; init; } = "0.0.0"; - - /// - /// Gets the target migration number. - /// - public int TargetMigrationNumber { get; init; } - - /// - /// Gets a value indicating whether the vault needs to be upgraded. - /// - public bool NeedsUpgrade { get; init; } - - /// - /// Gets the optional error message. - /// - public string? Error { get; init; } - } - /// /// Represents the result of a WebAuthn get credential operation. /// diff --git a/apps/server/AliasVault.Client/Services/JsInterop/Models/AliasVaultIdentity.cs b/apps/server/AliasVault.Client/Services/JsInterop/Models/AliasVaultIdentity.cs new file mode 100644 index 000000000..d84245fc7 --- /dev/null +++ b/apps/server/AliasVault.Client/Services/JsInterop/Models/AliasVaultIdentity.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.JsInterop.Models; + +/// +/// Represents the result of a JavaScript identity generator operation. +/// +public sealed class AliasVaultIdentity +{ + /// + /// Gets the first name. + /// + public string? FirstName { get; init; } + + /// + /// Gets the last name. + /// + public string? LastName { get; init; } + + /// + /// Gets the birth date. + /// + public string? BirthDate { get; init; } + + /// + /// Gets the email prefix. + /// + public string? EmailPrefix { get; init; } + + /// + /// Gets the nickname. + /// + public string? NickName { get; init; } + + /// + /// Gets the gender. + /// + public string? Gender { get; init; } +} diff --git a/apps/server/AliasVault.Client/Services/JsInterop/Models/SqlGenerationResult.cs b/apps/server/AliasVault.Client/Services/JsInterop/Models/SqlGenerationResult.cs new file mode 100644 index 000000000..999330af9 --- /dev/null +++ b/apps/server/AliasVault.Client/Services/JsInterop/Models/SqlGenerationResult.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.JsInterop.Models; + +/// +/// Represents the result of SQL generation for vault operations. +/// +public sealed class SqlGenerationResult +{ + /// + /// Gets a value indicating whether the SQL generation was successful. + /// + public bool Success { get; init; } + + /// + /// Gets the generated SQL commands to execute. + /// + public List SqlCommands { get; init; } = []; + + /// + /// Gets the vault version. + /// + public string Version { get; init; } = "0.0.0"; + + /// + /// Gets the migration number. + /// + public int MigrationNumber { get; init; } + + /// + /// Gets the optional error message. + /// + public string? Error { get; init; } +} diff --git a/apps/server/AliasVault.Client/Services/JsInterop/Models/SqlVaultVersion.cs b/apps/server/AliasVault.Client/Services/JsInterop/Models/SqlVaultVersion.cs new file mode 100644 index 000000000..2115d8212 --- /dev/null +++ b/apps/server/AliasVault.Client/Services/JsInterop/Models/SqlVaultVersion.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.Client.Services.JsInterop.Models; + +/// +/// Represents a vault version. +/// +public sealed class SqlVaultVersion +{ + /// + /// Gets the revision number. + /// + public int Revision { get; init; } + + /// + /// Gets the version. + /// + public string Version { get; init; } = string.Empty; + + /// + /// Gets the description. + /// + public string Description { get; init; } = string.Empty; + + /// + /// Gets the release date. + /// + public DateTime ReleaseDate { get; init; } = DateTime.MinValue; + + /// + /// Gets the AliasVault release version that this vault version was introduced in. + /// + public string ReleaseVersion { get; init; } = string.Empty; +} diff --git a/apps/server/AliasVault.Client/_Imports.razor b/apps/server/AliasVault.Client/_Imports.razor index 292c9e042..44dec45f1 100644 --- a/apps/server/AliasVault.Client/_Imports.razor +++ b/apps/server/AliasVault.Client/_Imports.razor @@ -26,6 +26,7 @@ @using AliasVault.Client.Services @using AliasVault.Client.Services.Auth @using AliasVault.Client.Services.Database +@using AliasVault.Client.Services.JsInterop; @using AliasVault.RazorComponents @using AliasVault.RazorComponents.Alerts @using AliasVault.RazorComponents.Buttons diff --git a/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.d.mts b/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.d.mts index 637729992..7a8cab8d0 100644 --- a/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.d.mts +++ b/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.d.mts @@ -15,9 +15,16 @@ interface IVaultVersion { */ description: string; /** - * Release date + * Date that this vault version was released. */ releaseDate: string; + /** + * The AliasVault release that this vault version was introduced in (e.g., "0.14.0"). + * This value is shown to the user in the UI instead of the actual vault version in order to + * avoid potential confusion. The "version" field is the actual vault database version. While + * this field is just for display purposes. + */ + releaseVersion: string; } /** @@ -81,11 +88,11 @@ declare class VaultSqlGenerator { /** * Get all available vault versions */ - getAvailableVersions(): IVaultVersion[]; + getAllVersions(): IVaultVersion[]; /** * Get current/latest vault version info */ - getCurrentVersion(): IVaultVersion; + getLatestVersion(): IVaultVersion; /** * Get specific migration SQL by migration number */ @@ -102,7 +109,9 @@ declare class VaultSqlGenerator { */ /** - * Available vault versions in chronological order + * All vault migrations/versions in chronological order. When adding a new migration, make sure to + * update the "releaseVersion" field to the correct AliasVault release version that introduced this + * migration. */ declare const VAULT_VERSIONS: IVaultVersion[]; diff --git a/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.d.ts b/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.d.ts index 637729992..7a8cab8d0 100644 --- a/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.d.ts +++ b/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.d.ts @@ -15,9 +15,16 @@ interface IVaultVersion { */ description: string; /** - * Release date + * Date that this vault version was released. */ releaseDate: string; + /** + * The AliasVault release that this vault version was introduced in (e.g., "0.14.0"). + * This value is shown to the user in the UI instead of the actual vault version in order to + * avoid potential confusion. The "version" field is the actual vault database version. While + * this field is just for display purposes. + */ + releaseVersion: string; } /** @@ -81,11 +88,11 @@ declare class VaultSqlGenerator { /** * Get all available vault versions */ - getAvailableVersions(): IVaultVersion[]; + getAllVersions(): IVaultVersion[]; /** * Get current/latest vault version info */ - getCurrentVersion(): IVaultVersion; + getLatestVersion(): IVaultVersion; /** * Get specific migration SQL by migration number */ @@ -102,7 +109,9 @@ declare class VaultSqlGenerator { */ /** - * Available vault versions in chronological order + * All vault migrations/versions in chronological order. When adding a new migration, make sure to + * update the "releaseVersion" field to the correct AliasVault release version that introduced this + * migration. */ declare const VAULT_VERSIONS: IVaultVersion[]; diff --git a/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.js b/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.js index a40a0100a..c1d85f1ab 100644 --- a/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.js +++ b/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.js @@ -480,55 +480,64 @@ var VAULT_VERSIONS = [ revision: 1, version: "1.0.1", description: "Empty Test Migration", - releaseDate: "2024-07-08" + releaseDate: "2024-07-08", + releaseVersion: "0.2.0" }, { revision: 2, version: "1.0.2", description: "Change Email Column", - releaseDate: "2024-07-11" + releaseDate: "2024-07-11", + releaseVersion: "0.3.0" }, { revision: 3, version: "1.1.0", description: "Add Pki Tables", - releaseDate: "2024-07-29" + releaseDate: "2024-07-29", + releaseVersion: "0.4.0" }, { revision: 4, version: "1.2.0", description: "Add Settings Table", - releaseDate: "2024-08-05" + releaseDate: "2024-08-05", + releaseVersion: "0.4.0" }, { revision: 5, version: "1.3.0", description: "Update Identity Structure", - releaseDate: "2024-08-05" + releaseDate: "2024-08-05", + releaseVersion: "0.5.0" }, { revision: 6, version: "1.3.1", description: "Make Username Optional", - releaseDate: "2024-08-12" + releaseDate: "2024-08-12", + releaseVersion: "0.5.0" }, { revision: 7, version: "1.4.0", description: "Add Sync Support", - releaseDate: "2024-09-16" + releaseDate: "2024-09-16", + releaseVersion: "0.6.0" }, { revision: 8, version: "1.4.1", description: "Rename Attachments Plural", - releaseDate: "2024-09-17" + releaseDate: "2024-09-17", + releaseVersion: "0.6.0" }, { revision: 9, version: "1.5.0", description: "Add Totp Codes", - releaseDate: "2025-03-10" + releaseDate: "2025-03-10", + releaseVersion: "0.14.0" } ]; @@ -704,13 +713,13 @@ var VaultSqlGenerator = class { /** * Get all available vault versions */ - getAvailableVersions() { + getAllVersions() { return [...VAULT_VERSIONS]; } /** * Get current/latest vault version info */ - getCurrentVersion() { + getLatestVersion() { return VAULT_VERSIONS[VAULT_VERSIONS.length - 1]; } /** diff --git a/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.mjs b/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.mjs index eba8ac27a..62ad40f6d 100644 --- a/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.mjs +++ b/apps/server/AliasVault.Client/wwwroot/js/dist/shared/vault-sql/index.mjs @@ -451,55 +451,64 @@ var VAULT_VERSIONS = [ revision: 1, version: "1.0.1", description: "Empty Test Migration", - releaseDate: "2024-07-08" + releaseDate: "2024-07-08", + releaseVersion: "0.2.0" }, { revision: 2, version: "1.0.2", description: "Change Email Column", - releaseDate: "2024-07-11" + releaseDate: "2024-07-11", + releaseVersion: "0.3.0" }, { revision: 3, version: "1.1.0", description: "Add Pki Tables", - releaseDate: "2024-07-29" + releaseDate: "2024-07-29", + releaseVersion: "0.4.0" }, { revision: 4, version: "1.2.0", description: "Add Settings Table", - releaseDate: "2024-08-05" + releaseDate: "2024-08-05", + releaseVersion: "0.4.0" }, { revision: 5, version: "1.3.0", description: "Update Identity Structure", - releaseDate: "2024-08-05" + releaseDate: "2024-08-05", + releaseVersion: "0.5.0" }, { revision: 6, version: "1.3.1", description: "Make Username Optional", - releaseDate: "2024-08-12" + releaseDate: "2024-08-12", + releaseVersion: "0.5.0" }, { revision: 7, version: "1.4.0", description: "Add Sync Support", - releaseDate: "2024-09-16" + releaseDate: "2024-09-16", + releaseVersion: "0.6.0" }, { revision: 8, version: "1.4.1", description: "Rename Attachments Plural", - releaseDate: "2024-09-17" + releaseDate: "2024-09-17", + releaseVersion: "0.6.0" }, { revision: 9, version: "1.5.0", description: "Add Totp Codes", - releaseDate: "2025-03-10" + releaseDate: "2025-03-10", + releaseVersion: "0.14.0" } ]; @@ -675,13 +684,13 @@ var VaultSqlGenerator = class { /** * Get all available vault versions */ - getAvailableVersions() { + getAllVersions() { return [...VAULT_VERSIONS]; } /** * Get current/latest vault version info */ - getCurrentVersion() { + getLatestVersion() { return VAULT_VERSIONS[VAULT_VERSIONS.length - 1]; } /** diff --git a/apps/server/Databases/AliasClientDb/Scripts/convert-sql-to-ts.sh b/apps/server/Databases/AliasClientDb/Scripts/convert-sql-to-ts.sh index 9eb72e55e..bd6e9f68b 100755 --- a/apps/server/Databases/AliasClientDb/Scripts/convert-sql-to-ts.sh +++ b/apps/server/Databases/AliasClientDb/Scripts/convert-sql-to-ts.sh @@ -151,63 +151,6 @@ EOF echo "TypeScript constants file generated: $OUTPUT_FILE" echo "Total migrations processed: ${#migration_files[@]}" -# Now generate the vault versions file -echo "" -echo "Generating vault versions mapping..." - -# Start building the vault versions file -cat > "$VERSIONS_FILE" << 'EOF' -/** - * Vault version information - * Auto-generated from EF Core migration filenames - */ - -import { IVaultVersion } from "../types/VaultVersion"; - -/** - * Available vault versions in chronological order - */ -export const VAULT_VERSIONS: IVaultVersion[] = [ -EOF - -# Remove: declare -A versions_seen -last_version="" -for file in "${migration_files[@]}"; do - filename=$(basename "$file") - # Extract revision from filename prefix - revision=$(echo "$filename" | sed -n 's/^0*\([0-9]*\)_.*$/\1/p') - # Debug: print filename - echo "DEBUG: Processing file $filename" - last_segment=$(echo "$filename" | awk -F'_to_' '{print $NF}' | sed 's/\.sql$//') - echo "DEBUG: Last segment: $last_segment" - version_info=$(extract_version_info "$filename") - version=$(echo "$version_info" | cut -d'|' -f1) - description=$(echo "$version_info" | cut -d'|' -f2) - release_date=$(echo "$version_info" | cut -d'|' -f3) - - echo "DEBUG: $filename -> revision='$revision', version='$version', description='$description', release_date='$release_date'" - - # Only output if version is not empty and not a duplicate of the last one - if [ -n "$version" ] && [ -n "$description" ] && [ "$version" != "$last_version" ]; then - last_version="$version" - echo "Found version $version: $description ($release_date)" - cat >> "$VERSIONS_FILE" << EOF - { - revision: $revision, - version: '$version', - description: '$description', - releaseDate: '$release_date' - }, -EOF - fi -done - -# Close the vault versions file -cat >> "$VERSIONS_FILE" << 'EOF' -]; -EOF - -echo "Vault versions file generated: $VERSIONS_FILE" # Copy generated files to shared vault-sql directory echo "" @@ -223,9 +166,6 @@ fi # Copy SqlConstants.ts copy_to_shared_sql "$OUTPUT_FILE" "$SHARED_SQL_DIR/SqlConstants.ts" -# Copy VaultVersions.ts -copy_to_shared_sql "$VERSIONS_FILE" "$SHARED_SQL_DIR/VaultVersions.ts" - echo "" echo "Done!" diff --git a/apps/server/Databases/AliasClientDb/Scripts/run-all.sh b/apps/server/Databases/AliasClientDb/Scripts/run-all.sh index c4cb71c72..10f9cd7c6 100755 --- a/apps/server/Databases/AliasClientDb/Scripts/run-all.sh +++ b/apps/server/Databases/AliasClientDb/Scripts/run-all.sh @@ -42,6 +42,8 @@ echo "- SQL files: MigrationSql/" echo "- TypeScript files: MigrationTs/" echo "" echo "The TypeScript files have been copied to the shared vault-sql directory." -echo "Make sure to rebuild the vault-sql library and test it in the client apps." echo "" +echo "Next, make sure to add the new vault SQL migrations to the VaultVersions.ts file in the vault-sql library." +echo "" +echo "Afterwards, run the following command to build and distribute the vault-sql library:" echo "shared/build-and-distribute.sh" \ No newline at end of file diff --git a/docs/misc/dev/upgrade-ef-client-model.md b/docs/misc/dev/upgrade-ef-client-model.md index 2e8221d3e..0350d0fe1 100644 --- a/docs/misc/dev/upgrade-ef-client-model.md +++ b/docs/misc/dev/upgrade-ef-client-model.md @@ -12,11 +12,12 @@ This guide explains how to upgrade the AliasVault client database structure. The ## Overview -The upgrade process involves three main steps: +The upgrade process involves four main steps: 1. **Update .NET Entity Framework model** - Modify the EF model and create migrations 2. **Generate SQL scripts** - Convert EF migrations to SQL scripts for cross-platform use -3. **Rebuild vault-sql shared library** - Compile and distribute the updated SQL scripts +3. **Add new migrations to VaultVersions** - Manually update the TypeScript version list +4. **Rebuild vault-sql shared library** - Compile and distribute the updated SQL scripts --- @@ -55,16 +56,25 @@ The script will: --- -## 3. Rebuild vault-sql Shared Library +## 3. Add New Migrations to VaultVersions -### Step 3.1: Compile and Distribute +### Step 3.1: Update VaultVersions.ts +Manually update the `shared/vault-sql/src/sql/VaultVersions.ts` file to include the new migration(s) with the proper fields. + +This step ensures that the TypeScript version list is synchronized with the generated SQL scripts and maintains proper version tracking across all client platforms. This list is also used by the client app to detect if there are new migrations that should be applied, and what information to show to the user. + +--- + +## 4. Rebuild vault-sql Shared Library + +### Step 4.1: Compile and Distribute The vault-sql TypeScript library is consumed by web apps, browser extensions, and mobile apps for vault creation and updates. After generating the SQL scripts, rebuild the library: ```bash shared/build-and-distribute.sh ``` -### Step 3.2: Verify Distribution +### Step 4.2: Verify Distribution Ensure the updated library is properly distributed to all consuming applications. --- diff --git a/shared/vault-sql/src/__tests__/VaultSqlGenerator.test.ts b/shared/vault-sql/src/__tests__/VaultSqlGenerator.test.ts index 3206c5196..a9dddce38 100644 --- a/shared/vault-sql/src/__tests__/VaultSqlGenerator.test.ts +++ b/shared/vault-sql/src/__tests__/VaultSqlGenerator.test.ts @@ -1,16 +1,17 @@ import { describe, it, expect } from 'vitest'; -import { VaultSqlGenerator } from '../sql/VaultSqlGenerator.js'; -import { CURRENT_VAULT_VERSION } from '../types/VaultVersion.js'; +import { VaultSqlGenerator } from '../sql/VaultSqlGenerator'; describe('VaultSqlGenerator', () => { + const generator = new VaultSqlGenerator(); + describe('getCreateVaultSql', () => { it('should return SQL commands for creating a new vault', () => { - const result = VaultSqlGenerator.getCreateVaultSql(); + const result = generator.getCreateVaultSql(); expect(result.success).toBe(true); expect(result.sqlCommands.length).toBeGreaterThan(0); - expect(result.version).toBe(CURRENT_VAULT_VERSION.version); - expect(result.migrationNumber).toBe(CURRENT_VAULT_VERSION.migrationNumber); + expect(result.version).toBe(generator.getLatestVersion().version); + expect(result.migrationNumber).toBe(generator.getLatestVersion().revision); // Should include PRAGMA and schema creation expect(result.sqlCommands[0]).toBe('PRAGMA foreign_keys = ON;'); @@ -21,16 +22,16 @@ describe('VaultSqlGenerator', () => { describe('getUpgradeVaultSql', () => { it('should return empty commands when vault is already at target version', () => { - const result = VaultSqlGenerator.getUpgradeVaultSql(CURRENT_VAULT_VERSION.migrationNumber); + const result = generator.getUpgradeVaultSql(generator.getLatestVersion().revision); expect(result.success).toBe(true); expect(result.sqlCommands).toEqual([]); - expect(result.version).toBe(CURRENT_VAULT_VERSION.version); - expect(result.migrationNumber).toBe(CURRENT_VAULT_VERSION.migrationNumber); + expect(result.version).toBe(generator.getLatestVersion().version); + expect(result.migrationNumber).toBe(generator.getLatestVersion().revision); }); it('should return upgrade commands for older version', () => { - const result = VaultSqlGenerator.getUpgradeVaultSql(1, 3); // Upgrade from v1.0.0 to v1.1.0 + const result = generator.getUpgradeVaultSql(1, 3); // Upgrade from v1.0.0 to v1.1.0 expect(result.success).toBe(true); expect(result.sqlCommands.length).toBeGreaterThan(0); @@ -41,7 +42,7 @@ describe('VaultSqlGenerator', () => { }); it('should handle invalid target migration number', () => { - const result = VaultSqlGenerator.getUpgradeVaultSql(1, 999); + const result = generator.getUpgradeVaultSql(1, 999); expect(result.success).toBe(false); expect(result.error).toContain('not found'); @@ -50,14 +51,14 @@ describe('VaultSqlGenerator', () => { describe('getUpgradeToVersionSql', () => { it('should upgrade to specific version', () => { - const result = VaultSqlGenerator.getUpgradeToVersionSql(1, '1.2.0'); + const result = generator.getUpgradeToVersionSql(1, '1.2.0'); expect(result.success).toBe(true); expect(result.sqlCommands.length).toBeGreaterThan(0); }); it('should handle invalid version', () => { - const result = VaultSqlGenerator.getUpgradeToVersionSql(1, '99.99.99'); + const result = generator.getUpgradeToVersionSql(1, '99.99.99'); expect(result.success).toBe(false); expect(result.error).toContain('not found'); @@ -66,7 +67,7 @@ describe('VaultSqlGenerator', () => { describe('getVersionCheckSql', () => { it('should return SQL commands to check vault version', () => { - const commands = VaultSqlGenerator.getVersionCheckSql(); + const commands = generator.getVersionCheckSql(); expect(commands.length).toBe(3); expect(commands[0]).toContain('sqlite_master'); @@ -77,7 +78,7 @@ describe('VaultSqlGenerator', () => { describe('getVaultValidationSql', () => { it('should return SQL to validate vault structure', () => { - const sql = VaultSqlGenerator.getVaultValidationSql(); + const sql = generator.getVaultValidationSql(); expect(sql).toContain('sqlite_master'); expect(sql).toContain('Aliases'); @@ -88,15 +89,15 @@ describe('VaultSqlGenerator', () => { describe('parseVaultVersionInfo', () => { it('should parse vault version info correctly', () => { - const info = VaultSqlGenerator.parseVaultVersionInfo(true, '1.2.0', '4'); + const info = generator.parseVaultVersionInfo(true, '1.2.0', '4'); expect(info.currentVersion).toBe('1.2.0'); expect(info.currentMigrationNumber).toBe(4); - expect(info.needsUpgrade).toBe(info.currentMigrationNumber < CURRENT_VAULT_VERSION.migrationNumber); + expect(info.needsUpgrade).toBe(info.currentMigrationNumber < generator.getLatestVersion().revision); }); it('should handle missing Settings table', () => { - const info = VaultSqlGenerator.parseVaultVersionInfo(false); + const info = generator.parseVaultVersionInfo(false); expect(info.currentVersion).toBe('0.0.0'); expect(info.currentMigrationNumber).toBe(0); @@ -104,7 +105,7 @@ describe('VaultSqlGenerator', () => { }); it('should handle Settings table without version info', () => { - const info = VaultSqlGenerator.parseVaultVersionInfo(true); + const info = generator.parseVaultVersionInfo(true); expect(info.currentVersion).toBe('1.0.0'); expect(info.currentMigrationNumber).toBe(1); @@ -114,21 +115,21 @@ describe('VaultSqlGenerator', () => { describe('validateVaultStructure', () => { it('should validate vault with all required tables', () => { const tables = ['Aliases', 'Services', 'Credentials', 'Passwords', 'Settings']; - const isValid = VaultSqlGenerator.validateVaultStructure(tables); + const isValid = generator.validateVaultStructure(tables); expect(isValid).toBe(true); }); it('should reject vault with missing core tables', () => { const tables = ['Aliases', 'Services']; - const isValid = VaultSqlGenerator.validateVaultStructure(tables); + const isValid = generator.validateVaultStructure(tables); expect(isValid).toBe(false); }); it('should handle case-insensitive table names', () => { const tables = ['aliases', 'services', 'credentials', 'passwords']; - const isValid = VaultSqlGenerator.validateVaultStructure(tables); + const isValid = generator.validateVaultStructure(tables); expect(isValid).toBe(true); }); @@ -136,27 +137,27 @@ describe('VaultSqlGenerator', () => { describe('utility methods', () => { it('should return available versions', () => { - const versions = VaultSqlGenerator.getAvailableVersions(); + const versions = generator.getAllVersions(); expect(versions.length).toBeGreaterThan(0); expect(versions[0].version).toBe('1.0.0'); }); it('should return current version', () => { - const version = VaultSqlGenerator.getCurrentVersion(); + const version = generator.getLatestVersion(); - expect(version).toEqual(CURRENT_VAULT_VERSION); + expect(version).toEqual(generator.getLatestVersion()); }); it('should return migration SQL by number', () => { - const sql = VaultSqlGenerator.getMigrationSql(1); + const sql = generator.getMigrationSql(1); expect(sql).toBeDefined(); expect(sql).toContain('CREATE TABLE'); }); it('should return complete schema SQL', () => { - const sql = VaultSqlGenerator.getCompleteSchemaeSql(); + const sql = generator.getCompleteSchemaSql(); expect(sql).toBeDefined(); expect(sql).toContain('CREATE TABLE "Aliases"'); diff --git a/shared/vault-sql/src/sql/VaultSqlGenerator.ts b/shared/vault-sql/src/sql/VaultSqlGenerator.ts index e10848253..3351d81a9 100644 --- a/shared/vault-sql/src/sql/VaultSqlGenerator.ts +++ b/shared/vault-sql/src/sql/VaultSqlGenerator.ts @@ -234,14 +234,14 @@ export class VaultSqlGenerator { /** * Get all available vault versions */ - getAvailableVersions(): IVaultVersion[] { + getAllVersions(): IVaultVersion[] { return [...VAULT_VERSIONS]; } /** * Get current/latest vault version info */ - getCurrentVersion(): IVaultVersion { + getLatestVersion(): IVaultVersion { return VAULT_VERSIONS[VAULT_VERSIONS.length - 1]; } @@ -255,7 +255,7 @@ export class VaultSqlGenerator { /** * Get complete schema SQL for creating new vault */ - getCompleteSchemaeSql(): string { + getCompleteSchemaSql(): string { return COMPLETE_SCHEMA_SQL; } } \ No newline at end of file diff --git a/shared/vault-sql/src/sql/VaultVersions.ts b/shared/vault-sql/src/sql/VaultVersions.ts index 2b863e1d3..bf6a3c56a 100644 --- a/shared/vault-sql/src/sql/VaultVersions.ts +++ b/shared/vault-sql/src/sql/VaultVersions.ts @@ -6,61 +6,72 @@ import { IVaultVersion } from "../types/VaultVersion"; /** - * Available vault versions in chronological order + * All vault migrations/versions in chronological order. When adding a new migration, make sure to + * update the "releaseVersion" field to the correct AliasVault release version that introduced this + * migration. */ export const VAULT_VERSIONS: IVaultVersion[] = [ { revision: 1, version: '1.0.1', description: 'Empty Test Migration', - releaseDate: '2024-07-08' + releaseDate: '2024-07-08', + releaseVersion: '0.2.0', }, { revision: 2, version: '1.0.2', description: 'Change Email Column', - releaseDate: '2024-07-11' + releaseDate: '2024-07-11', + releaseVersion: '0.3.0', }, { revision: 3, version: '1.1.0', description: 'Add Pki Tables', - releaseDate: '2024-07-29' + releaseDate: '2024-07-29', + releaseVersion: '0.4.0', }, { revision: 4, version: '1.2.0', description: 'Add Settings Table', - releaseDate: '2024-08-05' + releaseDate: '2024-08-05', + releaseVersion: '0.4.0', }, { revision: 5, version: '1.3.0', description: 'Update Identity Structure', - releaseDate: '2024-08-05' + releaseDate: '2024-08-05', + releaseVersion: '0.5.0', }, { revision: 6, version: '1.3.1', description: 'Make Username Optional', - releaseDate: '2024-08-12' + releaseDate: '2024-08-12', + releaseVersion: '0.5.0', }, { revision: 7, version: '1.4.0', description: 'Add Sync Support', - releaseDate: '2024-09-16' + releaseDate: '2024-09-16', + releaseVersion: '0.6.0', }, { revision: 8, version: '1.4.1', description: 'Rename Attachments Plural', - releaseDate: '2024-09-17' + releaseDate: '2024-09-17', + releaseVersion: '0.6.0', }, { revision: 9, version: '1.5.0', description: 'Add Totp Codes', - releaseDate: '2025-03-10' + releaseDate: '2025-03-10', + releaseVersion: '0.14.0', }, ]; diff --git a/shared/vault-sql/src/types/VaultVersion.ts b/shared/vault-sql/src/types/VaultVersion.ts index 8085f4189..73f7783ad 100644 --- a/shared/vault-sql/src/types/VaultVersion.ts +++ b/shared/vault-sql/src/types/VaultVersion.ts @@ -18,7 +18,15 @@ export interface IVaultVersion { description: string; /** - * Release date + * Date that this vault version was released. */ releaseDate: string; + + /** + * The AliasVault release that this vault version was introduced in (e.g., "0.14.0"). + * This value is shown to the user in the UI instead of the actual vault version in order to + * avoid potential confusion. The "version" field is the actual vault database version. While + * this field is just for display purposes. + */ + releaseVersion: string; }