Refactor vault sql to include release info (#955)

This commit is contained in:
Leendert de Borst
2025-06-23 15:29:14 +02:00
committed by Leendert de Borst
parent 41b2a959ed
commit 822b95d940
21 changed files with 355 additions and 334 deletions

View File

@@ -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;

View File

@@ -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<RecentEmails> Logger

View File

@@ -4,6 +4,7 @@
@inject CredentialService CredentialService
@inject IJSRuntime JSRuntime
@inject AliasVault.Client.Services.QuickCreateStateService QuickCreateStateService
@using AliasVault.Client.Services.JsInterop.Models
@implements IAsyncDisposable
<PageHeader
@@ -359,13 +360,13 @@ else
private async Task GenerateRandomUsername()
{
// If current object is null, then we create a new random identity.
JsInteropService.AliasVaultIdentity identity;
AliasVaultIdentity identity;
if (Obj.Alias.FirstName is null && Obj.Alias.LastName is null && Obj.Alias.BirthDate == DateTime.MinValue)
{
// Create new Credential object to avoid modifying the original object
var randomIdentity = await CredentialService.GenerateRandomIdentityAsync(CreateNewCredentialObject());
identity = new JsInteropService.AliasVaultIdentity
identity = new AliasVaultIdentity
{
FirstName = randomIdentity.Alias.FirstName ?? string.Empty,
LastName = randomIdentity.Alias.LastName ?? string.Empty,
@@ -377,7 +378,7 @@ else
else
{
// Assemble identity model with the current values
identity = new JsInteropService.AliasVaultIdentity
identity = new AliasVaultIdentity
{
FirstName = Obj.Alias.FirstName ?? string.Empty,
LastName = Obj.Alias.LastName ?? string.Empty,

View File

@@ -1,4 +1,5 @@
@inject DbService DbService
@using AliasVault.Client.Services.JsInterop.Models
@inject DbService DbService
@inject GlobalNotificationService GlobalNotificationService
<div class="relative p-6 sm:p-8 bg-white dark:bg-gray-700 rounded-lg sm:shadow-xl max-w-md w-full mx-auto">
@@ -14,11 +15,11 @@
<div class="space-y-2">
<p class="flex justify-between items-center">
<span class="text-sm font-medium text-gray-600 dark:text-gray-400">Your vault:</span>
<span class="text-base font-bold text-blue-600 dark:text-blue-400">@CurrentVersion</span>
<span class="text-base font-bold text-blue-600 dark:text-blue-400">@(CurrentVersion?.ReleaseVersion ?? "...")</span>
</p>
<p class="flex justify-between items-center">
<span class="text-sm font-medium text-gray-600 dark:text-gray-400">AliasVault latest version:</span>
<span class="text-base font-bold text-green-600 dark:text-green-400">@LatestVersion</span>
<span class="text-base font-bold text-green-600 dark:text-green-400">@(LatestVersion?.ReleaseVersion ?? "...")</span>
</p>
</div>
</div>
@@ -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; }
/// <inheritdoc />
protected override async Task OnInitializedAsync()

View File

@@ -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.
/// </summary>
/// <returns>Version as string.</returns>
public async Task<string> GetCurrentDatabaseVersionAsync()
public async Task<SqlVaultVersion> 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",
};
}
/// <summary>
/// Get the latest available version (EF migration) as defined in code.
/// </summary>
/// <returns>Version as string.</returns>
public async Task<string> GetLatestDatabaseVersionAsync()
public async Task<SqlVaultVersion> 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,
};
}
/// <summary>
@@ -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,

View File

@@ -5,10 +5,11 @@
// </copyright>
//-----------------------------------------------------------------------
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;
/// <summary>
@@ -454,34 +455,58 @@ public sealed class JsInteropService(IJSRuntime jsRuntime)
}
}
/// <summary>
/// Gets all available vault versions.
/// </summary>
/// <returns>List of vault versions.</returns>
public async Task<List<SqlVaultVersion>> GetAllVaultVersionsAsync()
{
if (_vaultSqlInteropModule == null)
{
await InitializeAsync();
if (_vaultSqlInteropModule == null)
{
throw new InvalidOperationException("Failed to initialize identity generator module");
}
}
var vaultGenerator = await _vaultSqlInteropModule.InvokeAsync<IJSObjectReference>("CreateVaultSqlGenerator");
var result = await vaultGenerator.InvokeAsync<JsonElement>("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();
}
/// <summary>
/// Gets SQL commands to check current vault version.
/// </summary>
/// <returns>Array of SQL commands to execute.</returns>
public async Task<string[]> GetVersionCheckSqlAsync()
public async Task<SqlVaultVersion> 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<IJSObjectReference>("CreateVaultSqlGenerator");
var result = await vaultGenerator.InvokeAsync<JsonElement>("getVersionCheckSql");
return result.EnumerateArray()
.Select(x => x.GetString() ?? string.Empty)
.Where(x => !string.IsNullOrEmpty(x))
.ToArray();
}
catch (JSException)
var vaultGenerator = await _vaultSqlInteropModule.InvokeAsync<IJSObjectReference>("CreateVaultSqlGenerator");
var result = await vaultGenerator.InvokeAsync<JsonElement>("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,
};
}
/// <summary>
@@ -558,52 +583,6 @@ public sealed class JsInteropService(IJSRuntime jsRuntime)
}
}
/// <summary>
/// Parses vault version information from query results.
/// </summary>
/// <param name="settingsTableExists">Whether Settings table exists.</param>
/// <param name="versionResult">Version query result.</param>
/// <param name="migrationResult">Migration number query result.</param>
/// <returns>Parsed vault version information.</returns>
public async Task<VaultVersionInfo> 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<IJSObjectReference>("CreateVaultSqlGenerator");
var result = await vaultGenerator.InvokeAsync<JsonElement>("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}",
};
}
}
/// <summary>
/// Validates vault structure from table names.
/// </summary>
@@ -631,109 +610,6 @@ public sealed class JsInteropService(IJSRuntime jsRuntime)
}
}
/// <summary>
/// Represents the result of a JavaScript identity generator operation.
/// </summary>
public sealed class AliasVaultIdentity
{
/// <summary>
/// Gets the first name.
/// </summary>
public string? FirstName { get; init; }
/// <summary>
/// Gets the last name.
/// </summary>
public string? LastName { get; init; }
/// <summary>
/// Gets the birth date.
/// </summary>
public string? BirthDate { get; init; }
/// <summary>
/// Gets the email prefix.
/// </summary>
public string? EmailPrefix { get; init; }
/// <summary>
/// Gets the nickname.
/// </summary>
public string? NickName { get; init; }
/// <summary>
/// Gets the gender.
/// </summary>
public string? Gender { get; init; }
}
/// <summary>
/// Represents the result of SQL generation for vault operations.
/// </summary>
public sealed class SqlGenerationResult
{
/// <summary>
/// Gets a value indicating whether the SQL generation was successful.
/// </summary>
public bool Success { get; init; }
/// <summary>
/// Gets the generated SQL commands to execute.
/// </summary>
public List<string> SqlCommands { get; init; } = [];
/// <summary>
/// Gets the vault version.
/// </summary>
public string Version { get; init; } = "0.0.0";
/// <summary>
/// Gets the migration number.
/// </summary>
public int MigrationNumber { get; init; }
/// <summary>
/// Gets the optional error message.
/// </summary>
public string? Error { get; init; }
}
/// <summary>
/// Represents vault version information.
/// </summary>
public sealed class VaultVersionInfo
{
/// <summary>
/// Gets the current vault version.
/// </summary>
public string CurrentVersion { get; init; } = "0.0.0";
/// <summary>
/// Gets the current migration number.
/// </summary>
public int CurrentMigrationNumber { get; init; }
/// <summary>
/// Gets the target vault version.
/// </summary>
public string TargetVersion { get; init; } = "0.0.0";
/// <summary>
/// Gets the target migration number.
/// </summary>
public int TargetMigrationNumber { get; init; }
/// <summary>
/// Gets a value indicating whether the vault needs to be upgraded.
/// </summary>
public bool NeedsUpgrade { get; init; }
/// <summary>
/// Gets the optional error message.
/// </summary>
public string? Error { get; init; }
}
/// <summary>
/// Represents the result of a WebAuthn get credential operation.
/// </summary>

View File

@@ -0,0 +1,44 @@
//-----------------------------------------------------------------------
// <copyright file="AliasVaultIdentity.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Client.Services.JsInterop.Models;
/// <summary>
/// Represents the result of a JavaScript identity generator operation.
/// </summary>
public sealed class AliasVaultIdentity
{
/// <summary>
/// Gets the first name.
/// </summary>
public string? FirstName { get; init; }
/// <summary>
/// Gets the last name.
/// </summary>
public string? LastName { get; init; }
/// <summary>
/// Gets the birth date.
/// </summary>
public string? BirthDate { get; init; }
/// <summary>
/// Gets the email prefix.
/// </summary>
public string? EmailPrefix { get; init; }
/// <summary>
/// Gets the nickname.
/// </summary>
public string? NickName { get; init; }
/// <summary>
/// Gets the gender.
/// </summary>
public string? Gender { get; init; }
}

View File

@@ -0,0 +1,39 @@
//-----------------------------------------------------------------------
// <copyright file="SqlGenerationResult.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Client.Services.JsInterop.Models;
/// <summary>
/// Represents the result of SQL generation for vault operations.
/// </summary>
public sealed class SqlGenerationResult
{
/// <summary>
/// Gets a value indicating whether the SQL generation was successful.
/// </summary>
public bool Success { get; init; }
/// <summary>
/// Gets the generated SQL commands to execute.
/// </summary>
public List<string> SqlCommands { get; init; } = [];
/// <summary>
/// Gets the vault version.
/// </summary>
public string Version { get; init; } = "0.0.0";
/// <summary>
/// Gets the migration number.
/// </summary>
public int MigrationNumber { get; init; }
/// <summary>
/// Gets the optional error message.
/// </summary>
public string? Error { get; init; }
}

View File

@@ -0,0 +1,39 @@
//-----------------------------------------------------------------------
// <copyright file="SqlVaultVersion.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Client.Services.JsInterop.Models;
/// <summary>
/// Represents a vault version.
/// </summary>
public sealed class SqlVaultVersion
{
/// <summary>
/// Gets the revision number.
/// </summary>
public int Revision { get; init; }
/// <summary>
/// Gets the version.
/// </summary>
public string Version { get; init; } = string.Empty;
/// <summary>
/// Gets the description.
/// </summary>
public string Description { get; init; } = string.Empty;
/// <summary>
/// Gets the release date.
/// </summary>
public DateTime ReleaseDate { get; init; } = DateTime.MinValue;
/// <summary>
/// Gets the AliasVault release version that this vault version was introduced in.
/// </summary>
public string ReleaseVersion { get; init; } = string.Empty;
}

View File

@@ -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

View File

@@ -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[];

View File

@@ -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[];

View File

@@ -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];
}
/**

View File

@@ -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];
}
/**

View File

@@ -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!"

View File

@@ -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"

View File

@@ -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.
---

View File

@@ -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"');

View File

@@ -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;
}
}

View File

@@ -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',
},
];

View File

@@ -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;
}