From 03fd047cb46d28cce51cfd5f689df85b225cd9d5 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 21 Dec 2024 11:56:08 +0100 Subject: [PATCH 01/62] Add postgresql docker container scaffolding (#190) --- Dockerfile.postgres | 6 ++++++ docker-compose.build.yml | 14 ++++++++++++++ docker-compose.yml | 26 ++++++++++++++++++++++++++ install.sh | 1 - postgresql.conf | 14 ++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.postgres create mode 100644 postgresql.conf diff --git a/Dockerfile.postgres b/Dockerfile.postgres new file mode 100644 index 00000000..9ccbd44c --- /dev/null +++ b/Dockerfile.postgres @@ -0,0 +1,6 @@ +FROM postgres:16-alpine + +# Add any custom PostgreSQL configurations if needed +COPY postgresql.conf /etc/postgresql/postgresql.conf + +CMD ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] \ No newline at end of file diff --git a/docker-compose.build.yml b/docker-compose.build.yml index 23a9cf89..9775bc47 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -34,3 +34,17 @@ services: build: context: . dockerfile: src/Services/AliasVault.TaskRunner/Dockerfile + + postgres: + image: aliasvault-postgres + build: + context: . + dockerfile: Dockerfile.postgres + ports: + - "5432:5432" + volumes: + - ./database:/var/lib/postgresql/data:rw + environment: + POSTGRES_DB: aliasvault + POSTGRES_USER: aliasvault + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} diff --git a/docker-compose.yml b/docker-compose.yml index c00261f0..6b949b0b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,6 +38,10 @@ services: restart: always env_file: - .env + depends_on: + - postgres + environment: + ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" admin: image: ghcr.io/lanedirt/aliasvault-admin:latest @@ -50,6 +54,10 @@ services: restart: always env_file: - .env + depends_on: + - postgres + environment: + ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" smtp: image: ghcr.io/lanedirt/aliasvault-smtp:latest @@ -62,6 +70,10 @@ services: restart: always env_file: - .env + depends_on: + - postgres + environment: + ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" task-runner: image: ghcr.io/lanedirt/aliasvault-task-runner:latest @@ -71,3 +83,17 @@ services: restart: always env_file: - .env + depends_on: + - postgres + environment: + ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" + + postgres: + image: ghcr.io/lanedirt/aliasvault-postgres:latest + volumes: + - ./database:/var/lib/postgresql/data:rw + environment: + POSTGRES_DB: aliasvault + POSTGRES_USER: aliasvault + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + restart: always diff --git a/install.sh b/install.sh index 3fa2472d..f46f50b0 100755 --- a/install.sh +++ b/install.sh @@ -689,7 +689,6 @@ handle_build() { set_default_ports || { printf "${RED}> Failed to set default ports${NC}\n"; exit 1; } set_public_registration || { printf "${RED}> Failed to set public registration${NC}\n"; exit 1; } - # Only generate admin password if not already set if ! grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" || [ -z "$(grep "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" | cut -d '=' -f2)" ]; then generate_admin_password || { printf "${RED}> Failed to generate admin password${NC}\n"; exit 1; } diff --git a/postgresql.conf b/postgresql.conf new file mode 100644 index 00000000..2c15fbeb --- /dev/null +++ b/postgresql.conf @@ -0,0 +1,14 @@ +# Basic PostgreSQL configuration +max_connections = 100 +shared_buffers = 128MB +dynamic_shared_memory_type = posix +max_wal_size = 1GB +min_wal_size = 80MB +log_timezone = 'UTC' +datestyle = 'iso, mdy' +timezone = 'UTC' +lc_messages = 'en_US.utf8' +lc_monetary = 'en_US.utf8' +lc_numeric = 'en_US.utf8' +lc_time = 'en_US.utf8' +default_text_search_config = 'pg_catalog.english' \ No newline at end of file From eb45358532ed2ff3d884b3b5ff6eafbb122339a2 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 21 Dec 2024 12:09:43 +0100 Subject: [PATCH 02/62] Update gitignore for db files (#190) --- .gitignore | 3 +++ docker-compose.build.yml | 2 +- docker-compose.yml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index d06a3a7d..06a5bfb4 100644 --- a/.gitignore +++ b/.gitignore @@ -407,3 +407,6 @@ certificates/letsencrypt/** docs/_site docs/vendor docs/.bundle + +# Database files +database/postgres diff --git a/docker-compose.build.yml b/docker-compose.build.yml index 9775bc47..dc6447a1 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -43,7 +43,7 @@ services: ports: - "5432:5432" volumes: - - ./database:/var/lib/postgresql/data:rw + - ./database/postgres:/var/lib/postgresql/data:rw environment: POSTGRES_DB: aliasvault POSTGRES_USER: aliasvault diff --git a/docker-compose.yml b/docker-compose.yml index 6b949b0b..7947271b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -91,7 +91,7 @@ services: postgres: image: ghcr.io/lanedirt/aliasvault-postgres:latest volumes: - - ./database:/var/lib/postgresql/data:rw + - ./database/postgres:/var/lib/postgresql/data:rw environment: POSTGRES_DB: aliasvault POSTGRES_USER: aliasvault From 3116aa5a1f57cc631e065d8ef923ca07b6e20b9f Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 21 Dec 2024 12:17:17 +0100 Subject: [PATCH 03/62] Update postgresql.conf (#190) --- postgresql.conf | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/postgresql.conf b/postgresql.conf index 2c15fbeb..9f8ef229 100644 --- a/postgresql.conf +++ b/postgresql.conf @@ -11,4 +11,8 @@ lc_messages = 'en_US.utf8' lc_monetary = 'en_US.utf8' lc_numeric = 'en_US.utf8' lc_time = 'en_US.utf8' -default_text_search_config = 'pg_catalog.english' \ No newline at end of file +default_text_search_config = 'pg_catalog.english' + +# Listen on all interfaces +listen_addresses = '*' +port = 5432 From 54d54f28b435cc302f9a993bb9cc244b209d1e99 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 21 Dec 2024 23:54:32 +0100 Subject: [PATCH 04/62] Move migrations for db engines to their respective folders (#190) --- .../AliasServerDb/AliasServerDb.csproj | 1 + .../AliasServerDb/AliasServerDbContext.cs | 57 +- .../AliasServerDbContextPostgresql.cs | 57 ++ .../AliasServerDbContextSqlite.cs | 107 +++ ...0241221225053_InitialMigration.Designer.cs | 906 ++++++++++++++++++ .../20241221225053_InitialMigration.cs | 602 ++++++++++++ ...sServerDbContextPostgresqlModelSnapshot.cs | 903 +++++++++++++++++ ...0240526135300_InitialMigration.Designer.cs | 2 +- .../20240526135300_InitialMigration.cs | 2 +- .../20240527082514_BasicEntities.Designer.cs | 2 +- .../20240527082514_BasicEntities.cs | 2 +- .../20240529140248_LoginUserId.Designer.cs | 2 +- .../20240529140248_LoginUserId.cs | 2 +- .../20240531142952_AddServiceLogo.Designer.cs | 2 +- .../20240531142952_AddServiceLogo.cs | 2 +- ...245_AddAspNetUserRefreshTokens.Designer.cs | 2 +- ...240603175245_AddAspNetUserRefreshTokens.cs | 2 +- ...200303_ChangeColumnDefinitions.Designer.cs | 2 +- .../20240616200303_ChangeColumnDefinitions.cs | 2 +- ...240621092501_AddAliasVaultUser.Designer.cs | 2 +- .../20240621092501_AddAliasVaultUser.cs | 2 +- .../20240624125214_AddVaultsTable.Designer.cs | 2 +- .../20240624125214_AddVaultsTable.cs | 2 +- ...40701104445_RemoveClientTables.Designer.cs | 2 +- .../20240701104445_RemoveClientTables.cs | 2 +- ...08113743_AddVaultVersionColumn.Designer.cs | 2 +- .../20240708113743_AddVaultVersionColumn.cs | 2 +- .../20240720151458_AddEmailTables.Designer.cs | 2 +- .../20240720151458_AddEmailTables.cs | 2 +- ...40720211546_AddAdminUsersTable.Designer.cs | 2 +- .../20240720211546_AddAdminUsersTable.cs | 2 +- ...05_AddAdminUserPasswordSetDate.Designer.cs | 2 +- ...40722144205_AddAdminUserPasswordSetDate.cs | 2 +- .../20240723194939_CreateLogTable.Designer.cs | 2 +- .../20240723194939_CreateLogTable.cs | 2 +- ...240725202058_WorkerStatusTable.Designer.cs | 2 +- .../20240725202058_WorkerStatusTable.cs | 2 +- ...40728110837_AddMetadataColumns.Designer.cs | 2 +- .../20240728110837_AddMetadataColumns.cs | 2 +- ...9150925_AddEncryptionKeyTables.Designer.cs | 2 +- .../20240729150925_AddEncryptionKeyTables.cs | 2 +- ...AddDataProtectionDatabaseTable.Designer.cs | 2 +- ...21164021_AddDataProtectionDatabaseTable.cs | 2 +- .../20240821204113_UpdateLogTable.Designer.cs | 2 +- .../20240821204113_UpdateLogTable.cs | 2 +- ...20240830190840_AddAuthLogTable.Designer.cs | 2 +- .../20240830190840_AddAuthLogTable.cs | 2 +- ...134755_MoveSrpColsToVaultTable.Designer.cs | 2 +- .../20240902134755_MoveSrpColsToVaultTable.cs | 2 +- ...644_RemoveSrpColsFromUserTable.Designer.cs | 2 +- ...240902172644_RemoveSrpColsFromUserTable.cs | 2 +- ...2833_AddVaultStatisticsColumns.Designer.cs | 2 +- ...0240906092833_AddVaultStatisticsColumns.cs | 2 +- ...159_AddVaultEncryptionSettings.Designer.cs | 2 +- ...240911174159_AddVaultEncryptionSettings.cs | 2 +- ...50600_AddRefreshTokenIpAddress.Designer.cs | 2 +- ...20240914150600_AddRefreshTokenIpAddress.cs | 2 +- ...6110323_AddVaultRevisionNumber.Designer.cs | 2 +- .../20240916110323_AddVaultRevisionNumber.cs | 2 +- ...10632_SetDefaultRevisionNumber.Designer.cs | 2 +- ...20240917210632_SetDefaultRevisionNumber.cs | 2 +- ...53012_DatetimeOffsetToDateTime.Designer.cs | 2 +- ...20241007153012_DatetimeOffsetToDateTime.cs | 2 +- ...erveEmailClaimsForDeletedUsers.Designer.cs | 2 +- ...5118_PreserveEmailClaimsForDeletedUsers.cs | 2 +- ...4121218_AddServerSettingsTable.Designer.cs | 2 +- .../20241204121218_AddServerSettingsTable.cs | 2 +- ...15131807_AddTaskRunnerJobTable.Designer.cs | 2 +- .../20241215131807_AddTaskRunnerJobTable.cs | 2 +- ...220164855_AddUserBlockedStatus.Designer.cs | 2 +- .../20241220164855_AddUserBlockedStatus.cs | 2 +- ...liasServerDbContextSqliteModelSnapshot.cs} | 6 +- 72 files changed, 2646 insertions(+), 121 deletions(-) create mode 100644 src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs create mode 100644 src/Databases/AliasServerDb/AliasServerDbContextSqlite.cs create mode 100644 src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.Designer.cs create mode 100644 src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.cs create mode 100644 src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240526135300_InitialMigration.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240526135300_InitialMigration.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240527082514_BasicEntities.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240527082514_BasicEntities.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240529140248_LoginUserId.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240529140248_LoginUserId.cs (97%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240531142952_AddServiceLogo.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240531142952_AddServiceLogo.cs (95%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240603175245_AddAspNetUserRefreshTokens.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240603175245_AddAspNetUserRefreshTokens.cs (97%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240616200303_ChangeColumnDefinitions.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240616200303_ChangeColumnDefinitions.cs (97%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240621092501_AddAliasVaultUser.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240621092501_AddAliasVaultUser.cs (95%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240624125214_AddVaultsTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240624125214_AddVaultsTable.cs (97%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240701104445_RemoveClientTables.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240701104445_RemoveClientTables.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240708113743_AddVaultVersionColumn.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240708113743_AddVaultVersionColumn.cs (93%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240720151458_AddEmailTables.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240720151458_AddEmailTables.cs (98%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240720211546_AddAdminUsersTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240720211546_AddAdminUsersTable.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240722144205_AddAdminUserPasswordSetDate.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240722144205_AddAdminUserPasswordSetDate.cs (93%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240723194939_CreateLogTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240723194939_CreateLogTable.cs (97%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240725202058_WorkerStatusTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240725202058_WorkerStatusTable.cs (96%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240728110837_AddMetadataColumns.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240728110837_AddMetadataColumns.cs (97%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240729150925_AddEncryptionKeyTables.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240729150925_AddEncryptionKeyTables.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240821164021_AddDataProtectionDatabaseTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240821164021_AddDataProtectionDatabaseTable.cs (95%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240821204113_UpdateLogTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240821204113_UpdateLogTable.cs (93%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240830190840_AddAuthLogTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240830190840_AddAuthLogTable.cs (98%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240902134755_MoveSrpColsToVaultTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240902134755_MoveSrpColsToVaultTable.cs (97%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240902172644_RemoveSrpColsFromUserTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240902172644_RemoveSrpColsFromUserTable.cs (96%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240906092833_AddVaultStatisticsColumns.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240906092833_AddVaultStatisticsColumns.cs (95%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240911174159_AddVaultEncryptionSettings.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240911174159_AddVaultEncryptionSettings.cs (96%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240914150600_AddRefreshTokenIpAddress.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240914150600_AddRefreshTokenIpAddress.cs (95%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240916110323_AddVaultRevisionNumber.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240916110323_AddVaultRevisionNumber.cs (93%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240917210632_SetDefaultRevisionNumber.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20240917210632_SetDefaultRevisionNumber.cs (96%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241007153012_DatetimeOffsetToDateTime.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241007153012_DatetimeOffsetToDateTime.cs (89%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241107205118_PreserveEmailClaimsForDeletedUsers.cs (98%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241204121218_AddServerSettingsTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241204121218_AddServerSettingsTable.cs (95%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241215131807_AddTaskRunnerJobTable.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241215131807_AddTaskRunnerJobTable.cs (96%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241220164855_AddUserBlockedStatus.Designer.cs (99%) rename src/Databases/AliasServerDb/Migrations/{ => SqliteMigrations}/20241220164855_AddUserBlockedStatus.cs (93%) rename src/Databases/AliasServerDb/Migrations/{AliasServerDbContextModelSnapshot.cs => SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs} (99%) diff --git a/src/Databases/AliasServerDb/AliasServerDb.csproj b/src/Databases/AliasServerDb/AliasServerDb.csproj index 2c14c33c..64798af3 100644 --- a/src/Databases/AliasServerDb/AliasServerDb.csproj +++ b/src/Databases/AliasServerDb/AliasServerDb.csproj @@ -31,6 +31,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Databases/AliasServerDb/AliasServerDbContext.cs b/src/Databases/AliasServerDb/AliasServerDbContext.cs index 58ce789a..24a3051d 100644 --- a/src/Databases/AliasServerDb/AliasServerDbContext.cs +++ b/src/Databases/AliasServerDb/AliasServerDbContext.cs @@ -18,24 +18,22 @@ namespace AliasServerDb; /// we have two separate user objects, one for the admin panel and one for the vault. We manually /// define the Identity tables in the OnModelCreating method. /// -public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyContext +public abstract class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyContext { /// /// Initializes a new instance of the class. /// - public AliasServerDbContext() + protected AliasServerDbContext() { - SetPragmaSettings(); } /// /// Initializes a new instance of the class. /// /// DbContextOptions. - public AliasServerDbContext(DbContextOptions options) + protected AliasServerDbContext(DbContextOptions options) : base(options) { - SetPragmaSettings(); } /// @@ -242,53 +240,4 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(l => l.UserId) .OnDelete(DeleteBehavior.Cascade); } - - /// - /// Sets up the connection string if it is not already configured. - /// - /// DbContextOptionsBuilder instance. - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - if (optionsBuilder.IsConfigured) - { - return; - } - - var configuration = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json") - .Build(); - - // Add SQLite connection with enhanced settings - var connectionString = configuration.GetConnectionString("AliasServerDbContext") + - ";Mode=ReadWriteCreate;Cache=Shared"; - - optionsBuilder - .UseSqlite(connectionString, options => options.CommandTimeout(60)) - .UseLazyLoadingProxies(); - } - - /// - /// Sets up the PRAGMA settings for SQLite. - /// - private void SetPragmaSettings() - { - var connection = Database.GetDbConnection(); - if (connection.State != System.Data.ConnectionState.Open) - { - connection.Open(); - } - - using (var command = connection.CreateCommand()) - { - // Increase busy timeout - command.CommandText = @" - PRAGMA busy_timeout = 30000; - PRAGMA journal_mode = WAL; - PRAGMA synchronous = NORMAL; - PRAGMA temp_store = MEMORY; - PRAGMA mmap_size = 1073741824;"; - command.ExecuteNonQuery(); - } - } } diff --git a/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs b/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs new file mode 100644 index 00000000..35281f26 --- /dev/null +++ b/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasServerDb; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +/// +/// PostgreSQL implementation of the AliasServerDbContext. +/// +public class AliasServerDbContextPostgresql : AliasServerDbContext +{ + /// + /// Initializes a new instance of the class. + /// + public AliasServerDbContextPostgresql() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// DbContextOptions. + public AliasServerDbContextPostgresql(DbContextOptions options) + : base(options) + { + } + + /// + /// Sets up the connection string if it is not already configured. + /// + /// DbContextOptionsBuilder instance. + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (optionsBuilder.IsConfigured) + { + return; + } + + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + // Add SQLite connection with enhanced settings + var connectionString = configuration.GetConnectionString("AliasServerDbContext"); + + optionsBuilder + .UseNpgsql(connectionString, options => options.CommandTimeout(60)) + .UseLazyLoadingProxies(); + } +} diff --git a/src/Databases/AliasServerDb/AliasServerDbContextSqlite.cs b/src/Databases/AliasServerDb/AliasServerDbContextSqlite.cs new file mode 100644 index 00000000..d93c355a --- /dev/null +++ b/src/Databases/AliasServerDb/AliasServerDbContextSqlite.cs @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasServerDb; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +/// +/// SQLite implementation of the AliasServerDbContext. +/// +public class AliasServerDbContextSqlite : AliasServerDbContext +{ + /// + /// Initializes a new instance of the class. + /// + public AliasServerDbContextSqlite() + { + SetPragmaSettings(); + } + + /// + /// Initializes a new instance of the class. + /// + /// DbContextOptions. + public AliasServerDbContextSqlite(DbContextOptions options) + : base(options) + { + SetPragmaSettings(); + } + + /// + /// The OnModelCreating method. + /// + /// ModelBuilder instance. + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Set TEXT as default column type for string properties because + // SQLite does not support varchar(max). + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + foreach (var property in entity.GetProperties()) + { + // SQLite does not support varchar(max) so we use TEXT. + if (property.ClrType == typeof(string) && property.GetMaxLength() == null) + { + property.SetColumnType("TEXT"); + } + } + } + } + + /// + /// Sets up the connection string if it is not already configured. + /// + /// DbContextOptionsBuilder instance. + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if (optionsBuilder.IsConfigured) + { + return; + } + + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + // Add SQLite connection with enhanced settings + var connectionString = configuration.GetConnectionString("AliasServerDbContext") + + ";Mode=ReadWriteCreate;Cache=Shared"; + + optionsBuilder + .UseSqlite(connectionString, options => options.CommandTimeout(60)) + .UseLazyLoadingProxies(); + } + + /// + /// Sets up the PRAGMA settings for SQLite. + /// + private void SetPragmaSettings() + { + var connection = Database.GetDbConnection(); + if (connection.State != System.Data.ConnectionState.Open) + { + connection.Open(); + } + + using (var command = connection.CreateCommand()) + { + // Increase busy timeout + command.CommandText = @" + PRAGMA busy_timeout = 30000; + PRAGMA journal_mode = WAL; + PRAGMA synchronous = NORMAL; + PRAGMA temp_store = MEMORY; + PRAGMA mmap_size = 1073741824;"; + command.ExecuteNonQuery(); + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.Designer.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.Designer.cs new file mode 100644 index 00000000..e3333e02 --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.Designer.cs @@ -0,0 +1,906 @@ +// +using System; +using AliasServerDb; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace AliasServerDb.Migrations.PostgresqlMigrations +{ + [DbContext(typeof(AliasServerDbContextPostgresql))] + [Migration("20241221225053_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AliasServerDb.AdminRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("NormalizedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AdminRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AdminUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastPasswordChanged") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AdminUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("NormalizedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Blocked") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordChangedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExpireDate") + .HasMaxLength(255) + .HasColumnType("timestamp with time zone"); + + b.Property("IpAddress") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("PreviousTokenValue") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AliasVaultUserRefreshTokens"); + }); + + modelBuilder.Entity("AliasServerDb.AuthLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdditionalInfo") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Browser") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DeviceType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("EventType") + .HasColumnType("nvarchar(50)"); + + b.Property("FailureReason") + .HasColumnType("integer"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsSuccess") + .HasColumnType("boolean"); + + b.Property("IsSuspiciousActivity") + .HasColumnType("boolean"); + + b.Property("OperatingSystem") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("RequestPath") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("UserAgent") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EventType" }, "IX_EventType"); + + b.HasIndex(new[] { "IpAddress" }, "IX_IpAddress"); + + b.HasIndex(new[] { "Timestamp" }, "IX_Timestamp"); + + b.HasIndex(new[] { "Username", "IsSuccess", "Timestamp" }, "IX_Username_IsSuccess_Timestamp") + .IsDescending(false, false, true); + + b.HasIndex(new[] { "Username", "Timestamp" }, "IX_Username_Timestamp") + .IsDescending(false, true); + + b.ToTable("AuthLogs"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DateSystem") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedSymmetricKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("From") + .IsRequired() + .HasColumnType("text"); + + b.Property("FromDomain") + .IsRequired() + .HasColumnType("text"); + + b.Property("FromLocal") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageHtml") + .HasColumnType("text"); + + b.Property("MessagePlain") + .HasColumnType("text"); + + b.Property("MessagePreview") + .HasColumnType("text"); + + b.Property("MessageSource") + .IsRequired() + .HasColumnType("text"); + + b.Property("PushNotificationSent") + .HasColumnType("boolean"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("To") + .IsRequired() + .HasColumnType("text"); + + b.Property("ToDomain") + .IsRequired() + .HasColumnType("text"); + + b.Property("ToLocal") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserEncryptionKeyId") + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("Visible") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("DateSystem"); + + b.HasIndex("PushNotificationSent"); + + b.HasIndex("ToLocal"); + + b.HasIndex("UserEncryptionKeyId"); + + b.HasIndex("Visible"); + + b.ToTable("Emails"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Bytes") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("EmailId") + .HasColumnType("integer"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("text"); + + b.Property("Filesize") + .HasColumnType("integer"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("EmailId"); + + b.ToTable("EmailAttachments"); + }); + + modelBuilder.Entity("AliasServerDb.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Application") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Exception") + .IsRequired() + .HasColumnType("text"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("LogEvent") + .IsRequired() + .HasColumnType("text") + .HasColumnName("LogEvent"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageTemplate") + .IsRequired() + .HasColumnType("text"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceContext") + .IsRequired() + .HasColumnType("nvarchar(255)"); + + b.Property("TimeStamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Application"); + + b.HasIndex("TimeStamp"); + + b.ToTable("Logs", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.ServerSetting", b => + { + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Key"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("AliasServerDb.TaskRunnerJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EndTime") + .HasColumnType("time without time zone"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("IsOnDemand") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(50)"); + + b.Property("RunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("time without time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("TaskRunnerJobs"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AddressDomain") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AddressLocal") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Address") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("UserEmailClaims"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserEncryptionKeys"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialsCount") + .HasColumnType("integer"); + + b.Property("EmailClaimsCount") + .HasColumnType("integer"); + + b.Property("EncryptionSettings") + .IsRequired() + .HasColumnType("text"); + + b.Property("EncryptionType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("integer"); + + b.Property("RevisionNumber") + .HasColumnType("bigint"); + + b.Property("Salt") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("VaultBlob") + .IsRequired() + .HasColumnType("text"); + + b.Property("Verifier") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("AliasVault.WorkerStatus.Database.WorkerServiceStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DesiredStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Heartbeat") + .HasColumnType("timestamp with time zone"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar"); + + b.HasKey("Id"); + + b.ToTable("WorkerServiceStatuses"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey") + .WithMany("Emails") + .HasForeignKey("UserEncryptionKeyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EncryptionKey"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.HasOne("AliasServerDb.Email", "Email") + .WithMany("Attachments") + .HasForeignKey("EmailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Email"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EmailClaims") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EncryptionKeys") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("Vaults") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Navigation("EmailClaims"); + + b.Navigation("EncryptionKeys"); + + b.Navigation("Vaults"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Navigation("Emails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.cs new file mode 100644 index 00000000..4ca18b5e --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.cs @@ -0,0 +1,602 @@ +// +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace AliasServerDb.Migrations.PostgresqlMigrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AdminRoles", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: true), + NormalizedName = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AdminRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AdminUsers", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + LastPasswordChanged = table.Column(type: "timestamp with time zone", nullable: true), + UserName = table.Column(type: "text", nullable: true), + NormalizedUserName = table.Column(type: "text", nullable: true), + Email = table.Column(type: "text", nullable: true), + NormalizedEmail = table.Column(type: "text", nullable: true), + EmailConfirmed = table.Column(type: "boolean", nullable: false), + PasswordHash = table.Column(type: "text", nullable: true), + SecurityStamp = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true), + PhoneNumber = table.Column(type: "text", nullable: true), + PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), + TwoFactorEnabled = table.Column(type: "boolean", nullable: false), + LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), + LockoutEnabled = table.Column(type: "boolean", nullable: false), + AccessFailedCount = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AdminUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AliasVaultRoles", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: true), + NormalizedName = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AliasVaultRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AliasVaultUsers", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + PasswordChangedAt = table.Column(type: "timestamp with time zone", nullable: false), + Blocked = table.Column(type: "boolean", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UserName = table.Column(type: "text", nullable: true), + NormalizedUserName = table.Column(type: "text", nullable: true), + Email = table.Column(type: "text", nullable: true), + NormalizedEmail = table.Column(type: "text", nullable: true), + EmailConfirmed = table.Column(type: "boolean", nullable: false), + PasswordHash = table.Column(type: "text", nullable: true), + SecurityStamp = table.Column(type: "text", nullable: true), + ConcurrencyStamp = table.Column(type: "text", nullable: true), + PhoneNumber = table.Column(type: "text", nullable: true), + PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), + TwoFactorEnabled = table.Column(type: "boolean", nullable: false), + LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), + LockoutEnabled = table.Column(type: "boolean", nullable: false), + AccessFailedCount = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AliasVaultUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AuthLogs", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Timestamp = table.Column(type: "timestamp with time zone", nullable: false), + Username = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + EventType = table.Column(type: "nvarchar(50)", nullable: false), + IsSuccess = table.Column(type: "boolean", nullable: false), + FailureReason = table.Column(type: "integer", nullable: true), + IpAddress = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + UserAgent = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + DeviceType = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + OperatingSystem = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Browser = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Country = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + AdditionalInfo = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + RequestPath = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + IsSuspiciousActivity = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AuthLogs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DataProtectionKeys", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + FriendlyName = table.Column(type: "text", nullable: true), + Xml = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DataProtectionKeys", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Logs", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Application = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + SourceContext = table.Column(type: "nvarchar(255)", nullable: false), + Message = table.Column(type: "text", nullable: false), + MessageTemplate = table.Column(type: "text", nullable: false), + Level = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + TimeStamp = table.Column(type: "timestamp with time zone", nullable: false), + Exception = table.Column(type: "text", nullable: false), + Properties = table.Column(type: "text", nullable: false), + LogEvent = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Logs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RoleClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + RoleId = table.Column(type: "text", nullable: true), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RoleClaims", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ServerSettings", + columns: table => new + { + Key = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Value = table.Column(type: "text", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ServerSettings", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "TaskRunnerJobs", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "nvarchar(50)", nullable: false), + RunDate = table.Column(type: "timestamp with time zone", nullable: false), + StartTime = table.Column(type: "time without time zone", nullable: false), + EndTime = table.Column(type: "time without time zone", nullable: true), + Status = table.Column(type: "integer", nullable: false), + ErrorMessage = table.Column(type: "text", nullable: true), + IsOnDemand = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TaskRunnerJobs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserClaims", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserId = table.Column(type: "text", nullable: true), + ClaimType = table.Column(type: "text", nullable: true), + ClaimValue = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserClaims", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "text", nullable: false), + ProviderKey = table.Column(type: "text", nullable: false), + ProviderDisplayName = table.Column(type: "text", nullable: true), + UserId = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserLogins", x => new { x.LoginProvider, x.ProviderKey }); + }); + + migrationBuilder.CreateTable( + name: "UserRoles", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + RoleId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId }); + }); + + migrationBuilder.CreateTable( + name: "UserTokens", + columns: table => new + { + UserId = table.Column(type: "text", nullable: false), + LoginProvider = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + Value = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + }); + + migrationBuilder.CreateTable( + name: "WorkerServiceStatuses", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ServiceName = table.Column(type: "varchar", maxLength: 255, nullable: false), + CurrentStatus = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + DesiredStatus = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + Heartbeat = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_WorkerServiceStatuses", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AliasVaultUserRefreshTokens", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + DeviceIdentifier = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + IpAddress = table.Column(type: "character varying(45)", maxLength: 45, nullable: true), + Value = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + PreviousTokenValue = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + ExpireDate = table.Column(type: "timestamp with time zone", maxLength: 255, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AliasVaultUserRefreshTokens", x => x.Id); + table.ForeignKey( + name: "FK_AliasVaultUserRefreshTokens_AliasVaultUsers_UserId", + column: x => x.UserId, + principalTable: "AliasVaultUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserEmailClaims", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + Address = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + AddressLocal = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + AddressDomain = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserEmailClaims", x => x.Id); + table.ForeignKey( + name: "FK_UserEmailClaims_AliasVaultUsers_UserId", + column: x => x.UserId, + principalTable: "AliasVaultUsers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "UserEncryptionKeys", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + PublicKey = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), + IsPrimary = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserEncryptionKeys", x => x.Id); + table.ForeignKey( + name: "FK_UserEncryptionKeys_AliasVaultUsers_UserId", + column: x => x.UserId, + principalTable: "AliasVaultUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Vaults", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + VaultBlob = table.Column(type: "text", nullable: false), + Version = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + RevisionNumber = table.Column(type: "bigint", nullable: false), + FileSize = table.Column(type: "integer", nullable: false), + Salt = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Verifier = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + CredentialsCount = table.Column(type: "integer", nullable: false), + EmailClaimsCount = table.Column(type: "integer", nullable: false), + EncryptionType = table.Column(type: "text", nullable: false), + EncryptionSettings = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Vaults", x => x.Id); + table.ForeignKey( + name: "FK_Vaults_AliasVaultUsers_UserId", + column: x => x.UserId, + principalTable: "AliasVaultUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Emails", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + UserEncryptionKeyId = table.Column(type: "uuid", maxLength: 255, nullable: false), + EncryptedSymmetricKey = table.Column(type: "text", nullable: false), + Subject = table.Column(type: "text", nullable: false), + From = table.Column(type: "text", nullable: false), + FromLocal = table.Column(type: "text", nullable: false), + FromDomain = table.Column(type: "text", nullable: false), + To = table.Column(type: "text", nullable: false), + ToLocal = table.Column(type: "text", nullable: false), + ToDomain = table.Column(type: "text", nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + DateSystem = table.Column(type: "timestamp with time zone", nullable: false), + MessageHtml = table.Column(type: "text", nullable: true), + MessagePlain = table.Column(type: "text", nullable: true), + MessagePreview = table.Column(type: "text", nullable: true), + MessageSource = table.Column(type: "text", nullable: false), + Visible = table.Column(type: "boolean", nullable: false), + PushNotificationSent = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Emails", x => x.Id); + table.ForeignKey( + name: "FK_Emails_UserEncryptionKeys_UserEncryptionKeyId", + column: x => x.UserEncryptionKeyId, + principalTable: "UserEncryptionKeys", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "EmailAttachments", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Bytes = table.Column(type: "bytea", nullable: false), + Filename = table.Column(type: "text", nullable: false), + MimeType = table.Column(type: "text", nullable: false), + Filesize = table.Column(type: "integer", nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + EmailId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EmailAttachments", x => x.Id); + table.ForeignKey( + name: "FK_EmailAttachments_Emails_EmailId", + column: x => x.EmailId, + principalTable: "Emails", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AliasVaultUserRefreshTokens_UserId", + table: "AliasVaultUserRefreshTokens", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_EventType", + table: "AuthLogs", + column: "EventType"); + + migrationBuilder.CreateIndex( + name: "IX_IpAddress", + table: "AuthLogs", + column: "IpAddress"); + + migrationBuilder.CreateIndex( + name: "IX_Timestamp", + table: "AuthLogs", + column: "Timestamp"); + + migrationBuilder.CreateIndex( + name: "IX_Username_IsSuccess_Timestamp", + table: "AuthLogs", + columns: new[] { "Username", "IsSuccess", "Timestamp" }, + descending: new[] { false, false, true }); + + migrationBuilder.CreateIndex( + name: "IX_Username_Timestamp", + table: "AuthLogs", + columns: new[] { "Username", "Timestamp" }, + descending: new[] { false, true }); + + migrationBuilder.CreateIndex( + name: "IX_EmailAttachments_EmailId", + table: "EmailAttachments", + column: "EmailId"); + + migrationBuilder.CreateIndex( + name: "IX_Emails_Date", + table: "Emails", + column: "Date"); + + migrationBuilder.CreateIndex( + name: "IX_Emails_DateSystem", + table: "Emails", + column: "DateSystem"); + + migrationBuilder.CreateIndex( + name: "IX_Emails_PushNotificationSent", + table: "Emails", + column: "PushNotificationSent"); + + migrationBuilder.CreateIndex( + name: "IX_Emails_ToLocal", + table: "Emails", + column: "ToLocal"); + + migrationBuilder.CreateIndex( + name: "IX_Emails_UserEncryptionKeyId", + table: "Emails", + column: "UserEncryptionKeyId"); + + migrationBuilder.CreateIndex( + name: "IX_Emails_Visible", + table: "Emails", + column: "Visible"); + + migrationBuilder.CreateIndex( + name: "IX_Logs_Application", + table: "Logs", + column: "Application"); + + migrationBuilder.CreateIndex( + name: "IX_Logs_TimeStamp", + table: "Logs", + column: "TimeStamp"); + + migrationBuilder.CreateIndex( + name: "IX_UserEmailClaims_Address", + table: "UserEmailClaims", + column: "Address", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserEmailClaims_UserId", + table: "UserEmailClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_UserEncryptionKeys_UserId", + table: "UserEncryptionKeys", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Vaults_UserId", + table: "Vaults", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AdminRoles"); + + migrationBuilder.DropTable( + name: "AdminUsers"); + + migrationBuilder.DropTable( + name: "AliasVaultRoles"); + + migrationBuilder.DropTable( + name: "AliasVaultUserRefreshTokens"); + + migrationBuilder.DropTable( + name: "AuthLogs"); + + migrationBuilder.DropTable( + name: "DataProtectionKeys"); + + migrationBuilder.DropTable( + name: "EmailAttachments"); + + migrationBuilder.DropTable( + name: "Logs"); + + migrationBuilder.DropTable( + name: "RoleClaims"); + + migrationBuilder.DropTable( + name: "ServerSettings"); + + migrationBuilder.DropTable( + name: "TaskRunnerJobs"); + + migrationBuilder.DropTable( + name: "UserClaims"); + + migrationBuilder.DropTable( + name: "UserEmailClaims"); + + migrationBuilder.DropTable( + name: "UserLogins"); + + migrationBuilder.DropTable( + name: "UserRoles"); + + migrationBuilder.DropTable( + name: "UserTokens"); + + migrationBuilder.DropTable( + name: "Vaults"); + + migrationBuilder.DropTable( + name: "WorkerServiceStatuses"); + + migrationBuilder.DropTable( + name: "Emails"); + + migrationBuilder.DropTable( + name: "UserEncryptionKeys"); + + migrationBuilder.DropTable( + name: "AliasVaultUsers"); + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs new file mode 100644 index 00000000..037cc84a --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs @@ -0,0 +1,903 @@ +// +using System; +using AliasServerDb; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace AliasServerDb.Migrations.PostgresqlMigrations +{ + [DbContext(typeof(AliasServerDbContextPostgresql))] + partial class AliasServerDbContextPostgresqlModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AliasServerDb.AdminRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("NormalizedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AdminRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AdminUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastPasswordChanged") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AdminUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("NormalizedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Blocked") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordChangedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExpireDate") + .HasMaxLength(255) + .HasColumnType("timestamp with time zone"); + + b.Property("IpAddress") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("PreviousTokenValue") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AliasVaultUserRefreshTokens"); + }); + + modelBuilder.Entity("AliasServerDb.AuthLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdditionalInfo") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Browser") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DeviceType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("EventType") + .HasColumnType("nvarchar(50)"); + + b.Property("FailureReason") + .HasColumnType("integer"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsSuccess") + .HasColumnType("boolean"); + + b.Property("IsSuspiciousActivity") + .HasColumnType("boolean"); + + b.Property("OperatingSystem") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("RequestPath") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("UserAgent") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EventType" }, "IX_EventType"); + + b.HasIndex(new[] { "IpAddress" }, "IX_IpAddress"); + + b.HasIndex(new[] { "Timestamp" }, "IX_Timestamp"); + + b.HasIndex(new[] { "Username", "IsSuccess", "Timestamp" }, "IX_Username_IsSuccess_Timestamp") + .IsDescending(false, false, true); + + b.HasIndex(new[] { "Username", "Timestamp" }, "IX_Username_Timestamp") + .IsDescending(false, true); + + b.ToTable("AuthLogs"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DateSystem") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedSymmetricKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("From") + .IsRequired() + .HasColumnType("text"); + + b.Property("FromDomain") + .IsRequired() + .HasColumnType("text"); + + b.Property("FromLocal") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageHtml") + .HasColumnType("text"); + + b.Property("MessagePlain") + .HasColumnType("text"); + + b.Property("MessagePreview") + .HasColumnType("text"); + + b.Property("MessageSource") + .IsRequired() + .HasColumnType("text"); + + b.Property("PushNotificationSent") + .HasColumnType("boolean"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("To") + .IsRequired() + .HasColumnType("text"); + + b.Property("ToDomain") + .IsRequired() + .HasColumnType("text"); + + b.Property("ToLocal") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserEncryptionKeyId") + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("Visible") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("DateSystem"); + + b.HasIndex("PushNotificationSent"); + + b.HasIndex("ToLocal"); + + b.HasIndex("UserEncryptionKeyId"); + + b.HasIndex("Visible"); + + b.ToTable("Emails"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Bytes") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("EmailId") + .HasColumnType("integer"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("text"); + + b.Property("Filesize") + .HasColumnType("integer"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("EmailId"); + + b.ToTable("EmailAttachments"); + }); + + modelBuilder.Entity("AliasServerDb.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Application") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Exception") + .IsRequired() + .HasColumnType("text"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("LogEvent") + .IsRequired() + .HasColumnType("text") + .HasColumnName("LogEvent"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageTemplate") + .IsRequired() + .HasColumnType("text"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceContext") + .IsRequired() + .HasColumnType("nvarchar(255)"); + + b.Property("TimeStamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Application"); + + b.HasIndex("TimeStamp"); + + b.ToTable("Logs", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.ServerSetting", b => + { + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Key"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("AliasServerDb.TaskRunnerJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EndTime") + .HasColumnType("time without time zone"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("IsOnDemand") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(50)"); + + b.Property("RunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("time without time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("TaskRunnerJobs"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AddressDomain") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AddressLocal") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Address") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("UserEmailClaims"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserEncryptionKeys"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialsCount") + .HasColumnType("integer"); + + b.Property("EmailClaimsCount") + .HasColumnType("integer"); + + b.Property("EncryptionSettings") + .IsRequired() + .HasColumnType("text"); + + b.Property("EncryptionType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("integer"); + + b.Property("RevisionNumber") + .HasColumnType("bigint"); + + b.Property("Salt") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("VaultBlob") + .IsRequired() + .HasColumnType("text"); + + b.Property("Verifier") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("AliasVault.WorkerStatus.Database.WorkerServiceStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DesiredStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Heartbeat") + .HasColumnType("timestamp with time zone"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar"); + + b.HasKey("Id"); + + b.ToTable("WorkerServiceStatuses"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey") + .WithMany("Emails") + .HasForeignKey("UserEncryptionKeyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EncryptionKey"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.HasOne("AliasServerDb.Email", "Email") + .WithMany("Attachments") + .HasForeignKey("EmailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Email"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EmailClaims") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EncryptionKeys") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("Vaults") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Navigation("EmailClaims"); + + b.Navigation("EncryptionKeys"); + + b.Navigation("Vaults"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Navigation("Emails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/20240526135300_InitialMigration.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240526135300_InitialMigration.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.Designer.cs index 05a5d6e3..28966b43 100644 --- a/src/Databases/AliasServerDb/Migrations/20240526135300_InitialMigration.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240526135300_InitialMigration")] diff --git a/src/Databases/AliasServerDb/Migrations/20240526135300_InitialMigration.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240526135300_InitialMigration.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.cs index b2636637..5bf7dbdc 100644 --- a/src/Databases/AliasServerDb/Migrations/20240526135300_InitialMigration.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class InitialMigration : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240527082514_BasicEntities.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240527082514_BasicEntities.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.Designer.cs index 9b53a4c1..2f662aba 100644 --- a/src/Databases/AliasServerDb/Migrations/20240527082514_BasicEntities.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240527082514_BasicEntities")] diff --git a/src/Databases/AliasServerDb/Migrations/20240527082514_BasicEntities.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240527082514_BasicEntities.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.cs index 853d1aea..783fe83a 100644 --- a/src/Databases/AliasServerDb/Migrations/20240527082514_BasicEntities.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class BasicEntities : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240529140248_LoginUserId.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240529140248_LoginUserId.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.Designer.cs index f013a4e3..9c77a10a 100644 --- a/src/Databases/AliasServerDb/Migrations/20240529140248_LoginUserId.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240529140248_LoginUserId")] diff --git a/src/Databases/AliasServerDb/Migrations/20240529140248_LoginUserId.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.cs similarity index 97% rename from src/Databases/AliasServerDb/Migrations/20240529140248_LoginUserId.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.cs index 5ba2bfa4..a311874a 100644 --- a/src/Databases/AliasServerDb/Migrations/20240529140248_LoginUserId.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class LoginUserId : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240531142952_AddServiceLogo.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240531142952_AddServiceLogo.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.Designer.cs index 1f0557a4..e42ee77a 100644 --- a/src/Databases/AliasServerDb/Migrations/20240531142952_AddServiceLogo.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240531142952_AddServiceLogo")] diff --git a/src/Databases/AliasServerDb/Migrations/20240531142952_AddServiceLogo.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.cs similarity index 95% rename from src/Databases/AliasServerDb/Migrations/20240531142952_AddServiceLogo.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.cs index 67276774..cf20be6f 100644 --- a/src/Databases/AliasServerDb/Migrations/20240531142952_AddServiceLogo.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddServiceLogo : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs index 645f4ad2..d7499a59 100644 --- a/src/Databases/AliasServerDb/Migrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240603175245_AddAspNetUserRefreshTokens")] diff --git a/src/Databases/AliasServerDb/Migrations/20240603175245_AddAspNetUserRefreshTokens.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.cs similarity index 97% rename from src/Databases/AliasServerDb/Migrations/20240603175245_AddAspNetUserRefreshTokens.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.cs index f7cb494d..517a453a 100644 --- a/src/Databases/AliasServerDb/Migrations/20240603175245_AddAspNetUserRefreshTokens.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddAspNetUserRefreshTokens : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240616200303_ChangeColumnDefinitions.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240616200303_ChangeColumnDefinitions.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.Designer.cs index f05bac80..c2c13bcd 100644 --- a/src/Databases/AliasServerDb/Migrations/20240616200303_ChangeColumnDefinitions.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240616200303_ChangeColumnDefinitions")] diff --git a/src/Databases/AliasServerDb/Migrations/20240616200303_ChangeColumnDefinitions.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.cs similarity index 97% rename from src/Databases/AliasServerDb/Migrations/20240616200303_ChangeColumnDefinitions.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.cs index 9a450def..df27a5a0 100644 --- a/src/Databases/AliasServerDb/Migrations/20240616200303_ChangeColumnDefinitions.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class ChangeColumnDefinitions : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240621092501_AddAliasVaultUser.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240621092501_AddAliasVaultUser.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.Designer.cs index 871cfddf..c36a4632 100644 --- a/src/Databases/AliasServerDb/Migrations/20240621092501_AddAliasVaultUser.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240621092501_AddAliasVaultUser")] diff --git a/src/Databases/AliasServerDb/Migrations/20240621092501_AddAliasVaultUser.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.cs similarity index 95% rename from src/Databases/AliasServerDb/Migrations/20240621092501_AddAliasVaultUser.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.cs index ccf5cac2..47c48fd2 100644 --- a/src/Databases/AliasServerDb/Migrations/20240621092501_AddAliasVaultUser.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddAliasVaultUser : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240624125214_AddVaultsTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240624125214_AddVaultsTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.Designer.cs index 9520a900..f2f8528a 100644 --- a/src/Databases/AliasServerDb/Migrations/20240624125214_AddVaultsTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240624125214_AddVaultsTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20240624125214_AddVaultsTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.cs similarity index 97% rename from src/Databases/AliasServerDb/Migrations/20240624125214_AddVaultsTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.cs index 60981dd1..e9254a79 100644 --- a/src/Databases/AliasServerDb/Migrations/20240624125214_AddVaultsTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddVaultsTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240701104445_RemoveClientTables.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240701104445_RemoveClientTables.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.Designer.cs index ceda900b..28bfda56 100644 --- a/src/Databases/AliasServerDb/Migrations/20240701104445_RemoveClientTables.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240701104445_RemoveClientTables")] diff --git a/src/Databases/AliasServerDb/Migrations/20240701104445_RemoveClientTables.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240701104445_RemoveClientTables.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.cs index 26d7fdba..edf01f11 100644 --- a/src/Databases/AliasServerDb/Migrations/20240701104445_RemoveClientTables.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class RemoveClientTables : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240708113743_AddVaultVersionColumn.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240708113743_AddVaultVersionColumn.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.Designer.cs index 95de2b67..208ea92e 100644 --- a/src/Databases/AliasServerDb/Migrations/20240708113743_AddVaultVersionColumn.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240708113743_AddVaultVersionColumn")] diff --git a/src/Databases/AliasServerDb/Migrations/20240708113743_AddVaultVersionColumn.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.cs similarity index 93% rename from src/Databases/AliasServerDb/Migrations/20240708113743_AddVaultVersionColumn.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.cs index 4a15a9f4..0291f938 100644 --- a/src/Databases/AliasServerDb/Migrations/20240708113743_AddVaultVersionColumn.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddVaultVersionColumn : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240720151458_AddEmailTables.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240720151458_AddEmailTables.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.Designer.cs index f9668f78..869a7d19 100644 --- a/src/Databases/AliasServerDb/Migrations/20240720151458_AddEmailTables.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240718151458_AddEmailTables")] diff --git a/src/Databases/AliasServerDb/Migrations/20240720151458_AddEmailTables.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.cs similarity index 98% rename from src/Databases/AliasServerDb/Migrations/20240720151458_AddEmailTables.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.cs index c10d70c8..4c6bf16e 100644 --- a/src/Databases/AliasServerDb/Migrations/20240720151458_AddEmailTables.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddEmailTables : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240720211546_AddAdminUsersTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240720211546_AddAdminUsersTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.Designer.cs index eb1b8ad9..64b37311 100644 --- a/src/Databases/AliasServerDb/Migrations/20240720211546_AddAdminUsersTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240720211546_AddAdminUsersTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20240720211546_AddAdminUsersTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240720211546_AddAdminUsersTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.cs index 8717d79a..c08d2091 100644 --- a/src/Databases/AliasServerDb/Migrations/20240720211546_AddAdminUsersTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddAdminUsersTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs index d6664404..e9070ae2 100644 --- a/src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240722144205_AddAdminUserPasswordSetDate")] diff --git a/src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.cs similarity index 93% rename from src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.cs index 4949d21e..3eee6f4a 100644 --- a/src/Databases/AliasServerDb/Migrations/20240722144205_AddAdminUserPasswordSetDate.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddAdminUserPasswordSetDate : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.Designer.cs index 97b3cf9e..3ebc1741 100644 --- a/src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240723194939_CreateLogTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.cs similarity index 97% rename from src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.cs index 0343633d..35150732 100644 --- a/src/Databases/AliasServerDb/Migrations/20240723194939_CreateLogTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class CreateLogTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.Designer.cs index 0f219651..c46b2d31 100644 --- a/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240725202058_WorkerStatusTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.cs similarity index 96% rename from src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.cs index 4252d1ed..12827eba 100644 --- a/src/Databases/AliasServerDb/Migrations/20240725202058_WorkerStatusTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class WorkerStatusTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240728110837_AddMetadataColumns.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240728110837_AddMetadataColumns.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.Designer.cs index 55355f27..f110bb43 100644 --- a/src/Databases/AliasServerDb/Migrations/20240728110837_AddMetadataColumns.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240728110837_AddMetadataColumns")] diff --git a/src/Databases/AliasServerDb/Migrations/20240728110837_AddMetadataColumns.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.cs similarity index 97% rename from src/Databases/AliasServerDb/Migrations/20240728110837_AddMetadataColumns.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.cs index e9e3ad69..ce4f9346 100644 --- a/src/Databases/AliasServerDb/Migrations/20240728110837_AddMetadataColumns.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddMetadataColumns : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240729150925_AddEncryptionKeyTables.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240729150925_AddEncryptionKeyTables.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.Designer.cs index b3aba771..e6ec564d 100644 --- a/src/Databases/AliasServerDb/Migrations/20240729150925_AddEncryptionKeyTables.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240729150925_AddEncryptionKeyTables")] diff --git a/src/Databases/AliasServerDb/Migrations/20240729150925_AddEncryptionKeyTables.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240729150925_AddEncryptionKeyTables.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.cs index eeae4f61..b001f555 100644 --- a/src/Databases/AliasServerDb/Migrations/20240729150925_AddEncryptionKeyTables.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddEncryptionKeyTables : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs index 8d7c1c53..f3beef55 100644 --- a/src/Databases/AliasServerDb/Migrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240821164021_AddDataProtectionDatabaseTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20240821164021_AddDataProtectionDatabaseTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.cs similarity index 95% rename from src/Databases/AliasServerDb/Migrations/20240821164021_AddDataProtectionDatabaseTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.cs index d19207b2..4ae5cd27 100644 --- a/src/Databases/AliasServerDb/Migrations/20240821164021_AddDataProtectionDatabaseTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddDataProtectionDatabaseTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240821204113_UpdateLogTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240821204113_UpdateLogTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.Designer.cs index 9c5b42c6..afc0180c 100644 --- a/src/Databases/AliasServerDb/Migrations/20240821204113_UpdateLogTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240821204113_UpdateLogTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20240821204113_UpdateLogTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.cs similarity index 93% rename from src/Databases/AliasServerDb/Migrations/20240821204113_UpdateLogTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.cs index ebe32910..52af9e2d 100644 --- a/src/Databases/AliasServerDb/Migrations/20240821204113_UpdateLogTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class UpdateLogTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240830190840_AddAuthLogTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240830190840_AddAuthLogTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.Designer.cs index 9f715d3e..bbdcb6e3 100644 --- a/src/Databases/AliasServerDb/Migrations/20240830190840_AddAuthLogTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240830190840_AddAuthLogTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20240830190840_AddAuthLogTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.cs similarity index 98% rename from src/Databases/AliasServerDb/Migrations/20240830190840_AddAuthLogTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.cs index fe400de4..53cfaa3d 100644 --- a/src/Databases/AliasServerDb/Migrations/20240830190840_AddAuthLogTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddAuthLogTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs index 2093a95b..72979521 100644 --- a/src/Databases/AliasServerDb/Migrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240902134755_MoveSrpColsToVaultTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20240902134755_MoveSrpColsToVaultTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.cs similarity index 97% rename from src/Databases/AliasServerDb/Migrations/20240902134755_MoveSrpColsToVaultTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.cs index 92d5bca0..4c5703a1 100644 --- a/src/Databases/AliasServerDb/Migrations/20240902134755_MoveSrpColsToVaultTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class MoveSrpColsToVaultTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs index 2ba3429e..4ada3b91 100644 --- a/src/Databases/AliasServerDb/Migrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240902172644_RemoveSrpColsFromUserTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20240902172644_RemoveSrpColsFromUserTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.cs similarity index 96% rename from src/Databases/AliasServerDb/Migrations/20240902172644_RemoveSrpColsFromUserTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.cs index 806b9acf..0096eed6 100644 --- a/src/Databases/AliasServerDb/Migrations/20240902172644_RemoveSrpColsFromUserTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class RemoveSrpColsFromUserTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240906092833_AddVaultStatisticsColumns.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240906092833_AddVaultStatisticsColumns.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.Designer.cs index fdd1cdbd..5a00bdb2 100644 --- a/src/Databases/AliasServerDb/Migrations/20240906092833_AddVaultStatisticsColumns.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240906092833_AddVaultStatisticsColumns")] diff --git a/src/Databases/AliasServerDb/Migrations/20240906092833_AddVaultStatisticsColumns.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.cs similarity index 95% rename from src/Databases/AliasServerDb/Migrations/20240906092833_AddVaultStatisticsColumns.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.cs index b19b70cd..61ac1209 100644 --- a/src/Databases/AliasServerDb/Migrations/20240906092833_AddVaultStatisticsColumns.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddVaultStatisticsColumns : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240911174159_AddVaultEncryptionSettings.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240911174159_AddVaultEncryptionSettings.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.Designer.cs index 5aa9a25c..c57be6e2 100644 --- a/src/Databases/AliasServerDb/Migrations/20240911174159_AddVaultEncryptionSettings.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240911174159_AddVaultEncryptionSettings")] diff --git a/src/Databases/AliasServerDb/Migrations/20240911174159_AddVaultEncryptionSettings.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.cs similarity index 96% rename from src/Databases/AliasServerDb/Migrations/20240911174159_AddVaultEncryptionSettings.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.cs index da224d91..ba8ffd5f 100644 --- a/src/Databases/AliasServerDb/Migrations/20240911174159_AddVaultEncryptionSettings.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddVaultEncryptionSettings : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs index d248894e..218fd9be 100644 --- a/src/Databases/AliasServerDb/Migrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240914150600_AddRefreshTokenIpAddress")] diff --git a/src/Databases/AliasServerDb/Migrations/20240914150600_AddRefreshTokenIpAddress.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.cs similarity index 95% rename from src/Databases/AliasServerDb/Migrations/20240914150600_AddRefreshTokenIpAddress.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.cs index 652376cf..cde08808 100644 --- a/src/Databases/AliasServerDb/Migrations/20240914150600_AddRefreshTokenIpAddress.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddRefreshTokenIpAddress : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240916110323_AddVaultRevisionNumber.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240916110323_AddVaultRevisionNumber.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.Designer.cs index 18748a14..f92eefbf 100644 --- a/src/Databases/AliasServerDb/Migrations/20240916110323_AddVaultRevisionNumber.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240916110323_AddVaultRevisionNumber")] diff --git a/src/Databases/AliasServerDb/Migrations/20240916110323_AddVaultRevisionNumber.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.cs similarity index 93% rename from src/Databases/AliasServerDb/Migrations/20240916110323_AddVaultRevisionNumber.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.cs index e5a36de1..d735e550 100644 --- a/src/Databases/AliasServerDb/Migrations/20240916110323_AddVaultRevisionNumber.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddVaultRevisionNumber : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20240917210632_SetDefaultRevisionNumber.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20240917210632_SetDefaultRevisionNumber.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.Designer.cs index f0a180e8..a588bc94 100644 --- a/src/Databases/AliasServerDb/Migrations/20240917210632_SetDefaultRevisionNumber.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20240917210632_SetDefaultRevisionNumber")] diff --git a/src/Databases/AliasServerDb/Migrations/20240917210632_SetDefaultRevisionNumber.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.cs similarity index 96% rename from src/Databases/AliasServerDb/Migrations/20240917210632_SetDefaultRevisionNumber.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.cs index d2c34c47..4e7b0370 100644 --- a/src/Databases/AliasServerDb/Migrations/20240917210632_SetDefaultRevisionNumber.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// /// Migration to set default revision numbers for vaults. diff --git a/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs index 2467b829..2d0b3efe 100644 --- a/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20241007153012_DatetimeOffsetToDateTime")] diff --git a/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.cs similarity index 89% rename from src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.cs index c835ed73..b996dc51 100644 --- a/src/Databases/AliasServerDb/Migrations/20241007153012_DatetimeOffsetToDateTime.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class DatetimeOffsetToDateTime : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs index 60b08e15..2de5db5f 100644 --- a/src/Databases/AliasServerDb/Migrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20241107105118_PreserveEmailClaimsForDeletedUsers")] diff --git a/src/Databases/AliasServerDb/Migrations/20241107205118_PreserveEmailClaimsForDeletedUsers.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.cs similarity index 98% rename from src/Databases/AliasServerDb/Migrations/20241107205118_PreserveEmailClaimsForDeletedUsers.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.cs index e0a9cbf2..43f1bb02 100644 --- a/src/Databases/AliasServerDb/Migrations/20241107205118_PreserveEmailClaimsForDeletedUsers.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class PreserveEmailClaimsForDeletedUsers : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20241204121218_AddServerSettingsTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20241204121218_AddServerSettingsTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.Designer.cs index ec8da63c..f47f01df 100644 --- a/src/Databases/AliasServerDb/Migrations/20241204121218_AddServerSettingsTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20241204121218_AddServerSettingsTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20241204121218_AddServerSettingsTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.cs similarity index 95% rename from src/Databases/AliasServerDb/Migrations/20241204121218_AddServerSettingsTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.cs index 09b4cf09..48b8fd34 100644 --- a/src/Databases/AliasServerDb/Migrations/20241204121218_AddServerSettingsTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddServerSettingsTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20241215131807_AddTaskRunnerJobTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20241215131807_AddTaskRunnerJobTable.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.Designer.cs index 1926d91a..c2237aea 100644 --- a/src/Databases/AliasServerDb/Migrations/20241215131807_AddTaskRunnerJobTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20241215131807_AddTaskRunnerJobTable")] diff --git a/src/Databases/AliasServerDb/Migrations/20241215131807_AddTaskRunnerJobTable.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.cs similarity index 96% rename from src/Databases/AliasServerDb/Migrations/20241215131807_AddTaskRunnerJobTable.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.cs index 4c650df1..1d184fb7 100644 --- a/src/Databases/AliasServerDb/Migrations/20241215131807_AddTaskRunnerJobTable.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.cs @@ -4,7 +4,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddTaskRunnerJobTable : Migration diff --git a/src/Databases/AliasServerDb/Migrations/20241220164855_AddUserBlockedStatus.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/20241220164855_AddUserBlockedStatus.Designer.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.Designer.cs index 137594d5..877de97c 100644 --- a/src/Databases/AliasServerDb/Migrations/20241220164855_AddUserBlockedStatus.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.Designer.cs @@ -8,7 +8,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { [DbContext(typeof(AliasServerDbContext))] [Migration("20241220164855_AddUserBlockedStatus")] diff --git a/src/Databases/AliasServerDb/Migrations/20241220164855_AddUserBlockedStatus.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.cs similarity index 93% rename from src/Databases/AliasServerDb/Migrations/20241220164855_AddUserBlockedStatus.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.cs index e129dc8b..1707a87d 100644 --- a/src/Databases/AliasServerDb/Migrations/20241220164855_AddUserBlockedStatus.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.cs @@ -3,7 +3,7 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { /// public partial class AddUserBlockedStatus : Migration diff --git a/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs rename to src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs index e24bee53..517eca09 100644 --- a/src/Databases/AliasServerDb/Migrations/AliasServerDbContextModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs @@ -7,10 +7,10 @@ #nullable disable -namespace AliasServerDb.Migrations +namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] - partial class AliasServerDbContextModelSnapshot : ModelSnapshot + [DbContext(typeof(AliasServerDbContextSqlite))] + partial class AliasServerDbContextSqliteModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { From 8fbd10caaac8992567e8ca3f8cd954f8f1d4bb90 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 22 Dec 2024 00:37:13 +0100 Subject: [PATCH 05/62] Update admin project to use new IAliasServerDbContextFactory (#190) --- src/AliasVault.Admin/Main/Pages/MainBase.cs | 4 +- src/AliasVault.Admin/Program.cs | 4 +- src/AliasVault.Admin/appsettings.json | 1 + .../AliasServerDb/AliasServerDbContext.cs | 6 +-- .../Configuration/DatabaseConfiguration.cs | 41 ++++++---------- .../IAliasServerDbContextFactory.cs | 27 +++++++++++ .../PostgresqlDbContextFactory.cs | 47 ++++++++++++++++++ .../AliasServerDb/SqliteDbContextFactory.cs | 48 +++++++++++++++++++ .../Services/ServerSettingsService.cs | 8 ++-- 9 files changed, 148 insertions(+), 38 deletions(-) create mode 100644 src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs create mode 100644 src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs create mode 100644 src/Databases/AliasServerDb/SqliteDbContextFactory.cs diff --git a/src/AliasVault.Admin/Main/Pages/MainBase.cs b/src/AliasVault.Admin/Main/Pages/MainBase.cs index c72d559c..b4431113 100644 --- a/src/AliasVault.Admin/Main/Pages/MainBase.cs +++ b/src/AliasVault.Admin/Main/Pages/MainBase.cs @@ -56,10 +56,10 @@ public abstract class MainBase : OwningComponentBase protected AliasServerDbContext DbContext { get; set; } = null!; /// - /// Gets or sets the AliasServerDbContextFactory instance. + /// Gets or sets the IAliasServerDbContextFactory instance. /// [Inject] - protected IDbContextFactory DbContextFactory { get; set; } = null!; + protected IAliasServerDbContextFactory DbContextFactory { get; set; } = null!; /// /// Gets or sets the GlobalLoadingService in order to manipulate the global loading spinner animation. diff --git a/src/AliasVault.Admin/Program.cs b/src/AliasVault.Admin/Program.cs index 876f4a55..1e272a76 100644 --- a/src/AliasVault.Admin/Program.cs +++ b/src/AliasVault.Admin/Program.cs @@ -69,7 +69,7 @@ options.LoginPath = "/user/login"; }); -builder.Services.AddAliasVaultSqliteConfiguration(); +builder.Services.AddAliasVaultDatabaseConfiguration(builder.Configuration); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddIdentityCore(options => { @@ -133,7 +133,7 @@ using (var scope = app.Services.CreateScope()) { var container = scope.ServiceProvider; - await using var db = await container.GetRequiredService>().CreateDbContextAsync(); + await using var db = container.GetRequiredService().CreateDbContext(); await db.Database.MigrateAsync(); await StartupTasks.CreateRolesIfNotExist(scope.ServiceProvider); diff --git a/src/AliasVault.Admin/appsettings.json b/src/AliasVault.Admin/appsettings.json index a0724464..bc79ae18 100644 --- a/src/AliasVault.Admin/appsettings.json +++ b/src/AliasVault.Admin/appsettings.json @@ -1,4 +1,5 @@ { + "DatabaseProvider": "sqlite", "ConnectionStrings": { "AliasServerDbContext": "Data Source=../../database/AliasServerDb.sqlite" }, diff --git a/src/Databases/AliasServerDb/AliasServerDbContext.cs b/src/Databases/AliasServerDb/AliasServerDbContext.cs index 24a3051d..ae1468bd 100644 --- a/src/Databases/AliasServerDb/AliasServerDbContext.cs +++ b/src/Databases/AliasServerDb/AliasServerDbContext.cs @@ -18,12 +18,12 @@ namespace AliasServerDb; /// we have two separate user objects, one for the admin panel and one for the vault. We manually /// define the Identity tables in the OnModelCreating method. /// -public abstract class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyContext +public class AliasServerDbContext : WorkerStatusDbContext, IDataProtectionKeyContext { /// /// Initializes a new instance of the class. /// - protected AliasServerDbContext() + public AliasServerDbContext() { } @@ -31,7 +31,7 @@ protected AliasServerDbContext() /// Initializes a new instance of the class. /// /// DbContextOptions. - protected AliasServerDbContext(DbContextOptions options) + public AliasServerDbContext(DbContextOptions options) : base(options) { } diff --git a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs index 448a91e6..2d4a67b7 100644 --- a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs +++ b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs @@ -7,9 +7,6 @@ namespace AliasServerDb.Configuration; -using System.Data.Common; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -22,39 +19,29 @@ public static class DatabaseConfiguration /// Configures SQLite for use with Entity Framework Core. /// /// The IServiceCollection to add the DbContext to. + /// The IConfiguration to use for the connection string. /// The IServiceCollection for method chaining. - public static IServiceCollection AddAliasVaultSqliteConfiguration(this IServiceCollection services) + public static IServiceCollection AddAliasVaultDatabaseConfiguration(this IServiceCollection services, IConfiguration configuration) { - var serviceProvider = services.BuildServiceProvider(); - var configuration = serviceProvider.GetRequiredService(); + var dbProvider = configuration.GetValue("DatabaseProvider")?.ToLower() ?? "sqlite"; - var connectionString = configuration.GetConnectionString("AliasServerDbContext"); - if (string.IsNullOrEmpty(connectionString)) + switch (dbProvider) { - throw new InvalidOperationException("Connection string 'AliasServerDbContext' not found."); + case "postgresql": + services.AddScoped(); + break; + case "sqlite": + default: + services.AddScoped(); + break; } - var sqliteConnectionStringBuilder = new SqliteConnectionStringBuilder(connectionString) + services.AddScoped(sp => { - Cache = SqliteCacheMode.Private, - Mode = SqliteOpenMode.ReadWriteCreate, - }; - - services.AddDbContextFactory(options => - { - options.UseSqlite(CreateAndConfigureSqliteConnection(sqliteConnectionStringBuilder.ConnectionString), sqliteOptions => - { - sqliteOptions.CommandTimeout(60); - }).UseLazyLoadingProxies(); + var factory = sp.GetRequiredService(); + return factory.CreateDbContext(); }); return services; } - - private static SqliteConnection CreateAndConfigureSqliteConnection(string connectionString) - { - var connection = new SqliteConnection(connectionString); - connection.Open(); - return connection; - } } diff --git a/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs b/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs new file mode 100644 index 00000000..3b19af1c --- /dev/null +++ b/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasServerDb; + +/// +/// The AliasServerDbContextFactory interface. +/// +public interface IAliasServerDbContextFactory +{ + /// + /// Creates a new AliasServerDbContext. + /// + /// The AliasServerDbContext. + AliasServerDbContext CreateDbContext(); + + /// + /// Creates a new AliasServerDbContext asynchronously. + /// + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the AliasServerDbContext. + Task CreateDbContextAsync(CancellationToken cancellationToken = default); +} diff --git a/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs new file mode 100644 index 00000000..262d040d --- /dev/null +++ b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasServerDb; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +/// +/// The AliasServerDbContextFactory interface. +/// +public class PostgresqlDbContextFactory : IAliasServerDbContextFactory +{ + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public PostgresqlDbContextFactory(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + public AliasServerDbContext CreateDbContext() + { + var optionsBuilder = new DbContextOptionsBuilder(); + var connectionString = _configuration.GetConnectionString("AliasServerDbContext"); + + optionsBuilder + .UseNpgsql(connectionString, options => options.CommandTimeout(60)) + .UseLazyLoadingProxies(); + + return new AliasServerDbContextPostgresql(optionsBuilder.Options); + } + + /// + public Task CreateDbContextAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(CreateDbContext()); + } +} diff --git a/src/Databases/AliasServerDb/SqliteDbContextFactory.cs b/src/Databases/AliasServerDb/SqliteDbContextFactory.cs new file mode 100644 index 00000000..69f08b50 --- /dev/null +++ b/src/Databases/AliasServerDb/SqliteDbContextFactory.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasServerDb; + +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +/// +/// The AliasServerDbContextFactory interface. +/// +public class SqliteDbContextFactory : IAliasServerDbContextFactory +{ + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public SqliteDbContextFactory(IConfiguration configuration) + { + _configuration = configuration; + } + + /// + public AliasServerDbContext CreateDbContext() + { + var optionsBuilder = new DbContextOptionsBuilder(); + var connectionString = _configuration.GetConnectionString("AliasServerDbContext") + + ";Mode=ReadWriteCreate;Cache=Shared"; + + optionsBuilder + .UseSqlite(connectionString, options => options.CommandTimeout(60)) + .UseLazyLoadingProxies(); + + return new AliasServerDbContextSqlite(optionsBuilder.Options); + } + + /// + public Task CreateDbContextAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(CreateDbContext()); + } +} diff --git a/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs b/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs index 5ac7622f..c0030514 100644 --- a/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs +++ b/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs @@ -20,7 +20,7 @@ namespace AliasVault.Shared.Server.Services; /// Server settings service. /// /// IDbContextFactory instance. -public class ServerSettingsService(IDbContextFactory dbContextFactory) +public class ServerSettingsService(IAliasServerDbContextFactory dbContextFactory) { private readonly Dictionary _cache = new(); @@ -36,7 +36,7 @@ public class ServerSettingsService(IDbContextFactory dbCon return cachedValue; } - await using var dbContext = await dbContextFactory.CreateDbContextAsync(CancellationToken.None); + await using var dbContext = dbContextFactory.CreateDbContext(); var setting = await dbContext.ServerSettings.FirstOrDefaultAsync(x => x.Key == key); _cache[key] = setting?.Value; @@ -57,7 +57,7 @@ public async Task SetSettingAsync(string key, string? value) return; } - await using var dbContext = await dbContextFactory.CreateDbContextAsync(CancellationToken.None); + await using var dbContext = dbContextFactory.CreateDbContext(); var setting = await dbContext.ServerSettings.FirstOrDefaultAsync(x => x.Key == key); var now = DateTime.UtcNow; @@ -96,7 +96,7 @@ public async Task SetSettingAsync(string key, string? value) /// The settings. public async Task GetAllSettingsAsync() { - await using var dbContext = await dbContextFactory.CreateDbContextAsync(CancellationToken.None); + await using var dbContext = dbContextFactory.CreateDbContext(); var settings = await dbContext.ServerSettings.ToDictionaryAsync(x => x.Key, x => x.Value); // Create model with defaults From e45866fa67a00c3d4bbcac8702225dc3d8f99149 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 22 Dec 2024 11:25:23 +0100 Subject: [PATCH 06/62] Update EF models to not use driver specific fields (#190) --- src/AliasVault.Admin/appsettings.json | 4 ++-- src/Databases/AliasServerDb/AliasServerDb.csproj | 4 ++++ src/Databases/AliasServerDb/AuthLog.cs | 1 - src/Databases/AliasServerDb/Log.cs | 6 +++--- ...=> 20241222101415_InitialMigration.Designer.cs} | 14 ++++++++------ ...ation.cs => 20241222101415_InitialMigration.cs} | 13 ++++++------- .../AliasServerDbContextPostgresqlModelSnapshot.cs | 12 +++++++----- src/Databases/AliasServerDb/TaskRunnerJob.cs | 2 +- 8 files changed, 31 insertions(+), 25 deletions(-) rename src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/{20241221225053_InitialMigration.Designer.cs => 20241222101415_InitialMigration.Designer.cs} (98%) rename src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/{20241221225053_InitialMigration.cs => 20241222101415_InitialMigration.cs} (98%) diff --git a/src/AliasVault.Admin/appsettings.json b/src/AliasVault.Admin/appsettings.json index bc79ae18..189a56ce 100644 --- a/src/AliasVault.Admin/appsettings.json +++ b/src/AliasVault.Admin/appsettings.json @@ -1,7 +1,7 @@ { - "DatabaseProvider": "sqlite", + "DatabaseProvider": "postgresql", "ConnectionStrings": { - "AliasServerDbContext": "Data Source=../../database/AliasServerDb.sqlite" + "AliasServerDbContext": "Host=localhost;Port=5432;Database=aliasvault;Username=aliasvault;Password=password" }, "Logging": { "LogLevel": { diff --git a/src/Databases/AliasServerDb/AliasServerDb.csproj b/src/Databases/AliasServerDb/AliasServerDb.csproj index 64798af3..3cd3165a 100644 --- a/src/Databases/AliasServerDb/AliasServerDb.csproj +++ b/src/Databases/AliasServerDb/AliasServerDb.csproj @@ -50,4 +50,8 @@ + + + + diff --git a/src/Databases/AliasServerDb/AuthLog.cs b/src/Databases/AliasServerDb/AuthLog.cs index 152956d6..07510007 100644 --- a/src/Databases/AliasServerDb/AuthLog.cs +++ b/src/Databases/AliasServerDb/AuthLog.cs @@ -91,7 +91,6 @@ public class AuthLog /// Gets or sets the type of authentication event (e.g., Login, Logout, FailedLogin). /// [Required] - [Column(TypeName = "nvarchar(50)")] public AuthEventType EventType { get; set; } /// diff --git a/src/Databases/AliasServerDb/Log.cs b/src/Databases/AliasServerDb/Log.cs index 90a22146..998279ee 100644 --- a/src/Databases/AliasServerDb/Log.cs +++ b/src/Databases/AliasServerDb/Log.cs @@ -26,13 +26,13 @@ public class Log /// Gets or sets the application name associated with the log entry. /// [Required] - [Column(TypeName = "nvarchar(50)")] + [MaxLength(50)] public string Application { get; set; } = null!; /// /// Gets or sets the source context that triggered this log message. /// - [Column(TypeName = "nvarchar(255)")] + [MaxLength(255)] public string SourceContext { get; set; } = null!; /// @@ -48,7 +48,7 @@ public class Log /// /// Gets or sets the log level. /// - [Column(TypeName = "nvarchar(128)")] + [MaxLength(128)] public string Level { get; set; } = null!; /// diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.Designer.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.Designer.cs similarity index 98% rename from src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.Designer.cs rename to src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.Designer.cs index e3333e02..9ebe53de 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.Designer.cs @@ -12,7 +12,7 @@ namespace AliasServerDb.Migrations.PostgresqlMigrations { [DbContext(typeof(AliasServerDbContextPostgresql))] - [Migration("20241221225053_InitialMigration")] + [Migration("20241222101415_InitialMigration")] partial class InitialMigration { /// @@ -253,7 +253,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("character varying(100)"); b.Property("EventType") - .HasColumnType("nvarchar(50)"); + .HasColumnType("integer"); b.Property("FailureReason") .HasColumnType("integer"); @@ -438,7 +438,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Application") .IsRequired() .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasColumnType("character varying(50)"); b.Property("Exception") .IsRequired() @@ -447,7 +447,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Level") .IsRequired() .HasMaxLength(128) - .HasColumnType("nvarchar(128)"); + .HasColumnType("character varying(128)"); b.Property("LogEvent") .IsRequired() @@ -468,7 +468,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("SourceContext") .IsRequired() - .HasColumnType("nvarchar(255)"); + .HasMaxLength(255) + .HasColumnType("character varying(255)"); b.Property("TimeStamp") .HasColumnType("timestamp with time zone"); @@ -521,7 +522,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() - .HasColumnType("nvarchar(50)"); + .HasMaxLength(50) + .HasColumnType("character varying(50)"); b.Property("RunDate") .HasColumnType("timestamp with time zone"); diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.cs similarity index 98% rename from src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.cs rename to src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.cs index 4ca18b5e..cf7dabf8 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241221225053_InitialMigration.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.cs @@ -1,5 +1,4 @@ -// -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; @@ -104,7 +103,7 @@ protected override void Up(MigrationBuilder migrationBuilder) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), Timestamp = table.Column(type: "timestamp with time zone", nullable: false), Username = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - EventType = table.Column(type: "nvarchar(50)", nullable: false), + EventType = table.Column(type: "integer", nullable: false), IsSuccess = table.Column(type: "boolean", nullable: false), FailureReason = table.Column(type: "integer", nullable: true), IpAddress = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), @@ -142,11 +141,11 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Application = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - SourceContext = table.Column(type: "nvarchar(255)", nullable: false), + Application = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + SourceContext = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), Message = table.Column(type: "text", nullable: false), MessageTemplate = table.Column(type: "text", nullable: false), - Level = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + Level = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), TimeStamp = table.Column(type: "timestamp with time zone", nullable: false), Exception = table.Column(type: "text", nullable: false), Properties = table.Column(type: "text", nullable: false), @@ -192,7 +191,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Name = table.Column(type: "nvarchar(50)", nullable: false), + Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), RunDate = table.Column(type: "timestamp with time zone", nullable: false), StartTime = table.Column(type: "time without time zone", nullable: false), EndTime = table.Column(type: "time without time zone", nullable: true), diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs index 037cc84a..efe471f3 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs @@ -250,7 +250,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(100)"); b.Property("EventType") - .HasColumnType("nvarchar(50)"); + .HasColumnType("integer"); b.Property("FailureReason") .HasColumnType("integer"); @@ -435,7 +435,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Application") .IsRequired() .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasColumnType("character varying(50)"); b.Property("Exception") .IsRequired() @@ -444,7 +444,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Level") .IsRequired() .HasMaxLength(128) - .HasColumnType("nvarchar(128)"); + .HasColumnType("character varying(128)"); b.Property("LogEvent") .IsRequired() @@ -465,7 +465,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("SourceContext") .IsRequired() - .HasColumnType("nvarchar(255)"); + .HasMaxLength(255) + .HasColumnType("character varying(255)"); b.Property("TimeStamp") .HasColumnType("timestamp with time zone"); @@ -518,7 +519,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() - .HasColumnType("nvarchar(50)"); + .HasMaxLength(50) + .HasColumnType("character varying(50)"); b.Property("RunDate") .HasColumnType("timestamp with time zone"); diff --git a/src/Databases/AliasServerDb/TaskRunnerJob.cs b/src/Databases/AliasServerDb/TaskRunnerJob.cs index 6c2033de..76b0b48b 100644 --- a/src/Databases/AliasServerDb/TaskRunnerJob.cs +++ b/src/Databases/AliasServerDb/TaskRunnerJob.cs @@ -27,7 +27,7 @@ public class TaskRunnerJob /// Gets or sets the name of the task runner job. /// [Required] - [Column(TypeName = "nvarchar(50)")] + [MaxLength(50)] public string Name { get; set; } = null!; /// From 9062cdc7011d1a45563ed16f74fe75582693c9d1 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 22 Dec 2024 11:53:22 +0100 Subject: [PATCH 07/62] Refactor admin project to use dbcontextfactory (#190) --- src/AliasVault.Admin/Auth/Pages/Login.razor | 1 - .../Components/ActiveUsersCard.razor | 6 ++- .../Components/EmailStatisticsCard.razor | 3 +- .../RegistrationStatisticsCard.razor | 3 +- src/AliasVault.Admin/Main/Pages/Emails.razor | 3 +- .../Main/Pages/Logging/Auth.razor | 8 +-- .../Main/Pages/Logging/General.razor | 11 +++-- src/AliasVault.Admin/Main/Pages/MainBase.cs | 15 +++--- .../Main/Pages/Users/Delete.razor | 8 +-- .../Main/Pages/Users/Users.razor | 3 +- .../Main/Pages/Users/View/Index.razor | 49 +++++++++++-------- src/AliasVault.Admin/Services/UserService.cs | 5 +- 12 files changed, 69 insertions(+), 46 deletions(-) diff --git a/src/AliasVault.Admin/Auth/Pages/Login.razor b/src/AliasVault.Admin/Auth/Pages/Login.razor index a7559c7a..00e99568 100644 --- a/src/AliasVault.Admin/Auth/Pages/Login.razor +++ b/src/AliasVault.Admin/Auth/Pages/Login.razor @@ -64,7 +64,6 @@ var user = await UserManager.FindByNameAsync(Input.UserName); if (user == null) { - await AuthLoggingService.LogAuthEventFailAsync(Input.UserName, AuthEventType.Login, AuthFailureReason.InvalidUsername); ServerValidationErrors.AddError("Error: Invalid login attempt."); return; diff --git a/src/AliasVault.Admin/Main/Pages/Dashboard/Components/ActiveUsersCard.razor b/src/AliasVault.Admin/Main/Pages/Dashboard/Components/ActiveUsersCard.razor index d051e0ae..4997edb4 100644 --- a/src/AliasVault.Admin/Main/Pages/Dashboard/Components/ActiveUsersCard.razor +++ b/src/AliasVault.Admin/Main/Pages/Dashboard/Components/ActiveUsersCard.razor @@ -105,11 +105,13 @@ // Get unique users who either: // 1. Have successful auth logs // 2. Have updated their vault - var activeUsers = await DbContext.AuthLogs + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + + var activeUsers = await dbContext.AuthLogs .Where(l => l.Timestamp >= since && l.IsSuccess) .Select(l => l.Username) .Union( - DbContext.Vaults + dbContext.Vaults .Where(v => v.UpdatedAt >= since) .Select(v => v.User.UserName!) ) diff --git a/src/AliasVault.Admin/Main/Pages/Dashboard/Components/EmailStatisticsCard.razor b/src/AliasVault.Admin/Main/Pages/Dashboard/Components/EmailStatisticsCard.razor index 51402fdd..56f60502 100644 --- a/src/AliasVault.Admin/Main/Pages/Dashboard/Components/EmailStatisticsCard.razor +++ b/src/AliasVault.Admin/Main/Pages/Dashboard/Components/EmailStatisticsCard.razor @@ -43,7 +43,8 @@ var last14Days = now.AddDays(-14); // Get email statistics - var emailQuery = DbContext.Emails.AsQueryable(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + var emailQuery = dbContext.Emails.AsQueryable(); EmailStats = new EmailStatistics { Last24Hours = await emailQuery.CountAsync(e => e.DateSystem >= last24Hours), diff --git a/src/AliasVault.Admin/Main/Pages/Dashboard/Components/RegistrationStatisticsCard.razor b/src/AliasVault.Admin/Main/Pages/Dashboard/Components/RegistrationStatisticsCard.razor index 85be0dd3..be103f98 100644 --- a/src/AliasVault.Admin/Main/Pages/Dashboard/Components/RegistrationStatisticsCard.razor +++ b/src/AliasVault.Admin/Main/Pages/Dashboard/Components/RegistrationStatisticsCard.razor @@ -43,7 +43,8 @@ var last14Days = now.AddDays(-14); // Get registration statistics - var registrationQuery = DbContext.AliasVaultUsers.AsQueryable(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + var registrationQuery = dbContext.AliasVaultUsers.AsQueryable(); RegistrationStats = new RegistrationStatistics { Last24Hours = await registrationQuery.CountAsync(u => u.CreatedAt >= last24Hours), diff --git a/src/AliasVault.Admin/Main/Pages/Emails.razor b/src/AliasVault.Admin/Main/Pages/Emails.razor index 8ba3e814..163d4ef9 100644 --- a/src/AliasVault.Admin/Main/Pages/Emails.razor +++ b/src/AliasVault.Admin/Main/Pages/Emails.razor @@ -90,7 +90,8 @@ else IsLoading = true; StateHasChanged(); - IQueryable query = DbContext.Emails; + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + IQueryable query = dbContext.Emails; // Apply sort switch (SortColumn) diff --git a/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor b/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor index 67a1d42d..24872775 100644 --- a/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor +++ b/src/AliasVault.Admin/Main/Pages/Logging/Auth.razor @@ -131,7 +131,8 @@ else IsLoading = true; StateHasChanged(); - var query = DbContext.AuthLogs.AsQueryable(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + var query = dbContext.AuthLogs.AsQueryable(); if (!string.IsNullOrEmpty(SearchTerm)) { @@ -215,8 +216,9 @@ else IsLoading = true; StateHasChanged(); - DbContext.AuthLogs.RemoveRange(DbContext.AuthLogs); - await DbContext.SaveChangesAsync(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + dbContext.AuthLogs.RemoveRange(dbContext.AuthLogs); + await dbContext.SaveChangesAsync(); await RefreshData(); IsLoading = false; diff --git a/src/AliasVault.Admin/Main/Pages/Logging/General.razor b/src/AliasVault.Admin/Main/Pages/Logging/General.razor index 7326f11a..73857ec8 100644 --- a/src/AliasVault.Admin/Main/Pages/Logging/General.razor +++ b/src/AliasVault.Admin/Main/Pages/Logging/General.razor @@ -135,7 +135,8 @@ else { if (firstRender) { - ServiceNames = await DbContext.Logs.Select(l => l.Application).Distinct().ToListAsync(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + ServiceNames = await dbContext.Logs.Select(l => l.Application).Distinct().ToListAsync(); await RefreshData(); } } @@ -151,7 +152,8 @@ else IsLoading = true; StateHasChanged(); - var query = DbContext.Logs.AsQueryable(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + var query = dbContext.Logs.AsQueryable(); if (!string.IsNullOrEmpty(SearchTerm)) { @@ -218,8 +220,9 @@ else IsLoading = true; StateHasChanged(); - DbContext.Logs.RemoveRange(DbContext.Logs); - await DbContext.SaveChangesAsync(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + dbContext.Logs.RemoveRange(dbContext.Logs); + await dbContext.SaveChangesAsync(); await RefreshData(); IsLoading = false; diff --git a/src/AliasVault.Admin/Main/Pages/MainBase.cs b/src/AliasVault.Admin/Main/Pages/MainBase.cs index b4431113..07cf8832 100644 --- a/src/AliasVault.Admin/Main/Pages/MainBase.cs +++ b/src/AliasVault.Admin/Main/Pages/MainBase.cs @@ -49,12 +49,6 @@ public abstract class MainBase : OwningComponentBase [Inject] protected JsInvokeService JsInvokeService { get; set; } = null!; - /// - /// Gets or sets the AliasServerDbContext instance. - /// - [Inject] - protected AliasServerDbContext DbContext { get; set; } = null!; - /// /// Gets or sets the IAliasServerDbContextFactory instance. /// @@ -90,6 +84,15 @@ public abstract class MainBase : OwningComponentBase /// protected List BreadcrumbItems { get; } = new(); + /// + /// Gets the AliasServerDbContext instance asynchronously. + /// + /// The AliasServerDbContext instance. + protected async Task GetDbContextAsync() + { + return await DbContextFactory.CreateDbContextAsync(); + } + /// protected override async Task OnInitializedAsync() { diff --git a/src/AliasVault.Admin/Main/Pages/Users/Delete.razor b/src/AliasVault.Admin/Main/Pages/Users/Delete.razor index 532c3f26..a36ef557 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/Delete.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/Delete.razor @@ -60,7 +60,8 @@ else if (firstRender) { // Load existing Obj. - Obj = await DbContext.AliasVaultUsers.FindAsync(Id); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + Obj = await dbContext.AliasVaultUsers.FindAsync(Id); // Hide loading spinner IsLoading = false; @@ -83,8 +84,9 @@ else // Add log entry. Logger.LogWarning("Deleted user {UserName} ({UserId}).", Obj.UserName, Obj.Id); - DbContext.AliasVaultUsers.Remove(Obj); - await DbContext.SaveChangesAsync(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + dbContext.AliasVaultUsers.Remove(Obj); + await dbContext.SaveChangesAsync(); GlobalNotificationService.AddSuccessMessage("User successfully deleted."); GlobalLoadingSpinner.Hide(); diff --git a/src/AliasVault.Admin/Main/Pages/Users/Users.razor b/src/AliasVault.Admin/Main/Pages/Users/Users.razor index 9d5d2666..290a86fa 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/Users.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/Users.razor @@ -115,7 +115,8 @@ else IsLoading = true; StateHasChanged(); - IQueryable query = DbContext.AliasVaultUsers; + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + IQueryable query = dbContext.AliasVaultUsers; if (SearchTerm.Length > 0) { diff --git a/src/AliasVault.Admin/Main/Pages/Users/View/Index.razor b/src/AliasVault.Admin/Main/Pages/Users/View/Index.razor index a36ba5cb..3cfd1840 100644 --- a/src/AliasVault.Admin/Main/Pages/Users/View/Index.razor +++ b/src/AliasVault.Admin/Main/Pages/Users/View/Index.razor @@ -135,10 +135,11 @@ else StateHasChanged(); // Load the aliases from the webapi via AliasService. - User = await DbContext.AliasVaultUsers.FindAsync(Id); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + User = await dbContext.AliasVaultUsers.FindAsync(Id); // Get count of user authenticator tokens. - TwoFactorKeysCount = await DbContext.UserTokens.CountAsync(x => x.UserId == User!.Id && x.Name == "AuthenticatorKey"); + TwoFactorKeysCount = await dbContext.UserTokens.CountAsync(x => x.UserId == User!.Id && x.Name == "AuthenticatorKey"); if (User is null) { @@ -149,7 +150,7 @@ else } // Load all active refresh tokens for this user to show which devices are logged in. - RefreshTokenList = await DbContext.AliasVaultUserRefreshTokens.Where(x => x.UserId == User.Id).Select(x => new AliasVaultUserRefreshToken() + RefreshTokenList = await dbContext.AliasVaultUserRefreshTokens.Where(x => x.UserId == User.Id).Select(x => new AliasVaultUserRefreshToken() { Id = x.Id, DeviceIdentifier = x.DeviceIdentifier, @@ -162,7 +163,7 @@ else .ToListAsync(); // Load all vaults for this user (do not load the actual file content for performance reasons). - VaultList = await DbContext.Vaults.Where(x => x.UserId == User.Id).Select(x => new Vault + VaultList = await dbContext.Vaults.Where(x => x.UserId == User.Id).Select(x => new Vault { Id = x.Id, Version = x.Version, @@ -182,7 +183,7 @@ else .ToListAsync(); // Load all email claims for this user. - EmailClaimList = await DbContext.UserEmailClaims + EmailClaimList = await dbContext.UserEmailClaims .Where(x => x.UserId == User.Id) .Select(x => new UserEmailClaimWithCount { @@ -192,7 +193,7 @@ else AddressDomain = x.AddressDomain, CreatedAt = x.CreatedAt, UpdatedAt = x.UpdatedAt, - EmailCount = DbContext.Emails.Count(e => e.To == x.Address) + EmailCount = dbContext.Emails.Count(e => e.To == x.Address) }) .OrderBy(x => x.CreatedAt) .ToListAsync(); @@ -206,12 +207,13 @@ else /// private async Task RevokeRefreshToken(AliasVaultUserRefreshToken entry) { - var token = await DbContext.AliasVaultUserRefreshTokens.FindAsync(entry.Id); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + var token = await dbContext.AliasVaultUserRefreshTokens.FindAsync(entry.Id); if (token != null) { - DbContext.AliasVaultUserRefreshTokens.Remove(token); - await DbContext.SaveChangesAsync(); + dbContext.AliasVaultUserRefreshTokens.Remove(token); + await dbContext.SaveChangesAsync(); await RefreshData(); } } @@ -222,12 +224,13 @@ else /// private async Task EnableTwoFactor() { - User = await DbContext.AliasVaultUsers.FindAsync(Id); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + User = await dbContext.AliasVaultUsers.FindAsync(Id); if (User != null) { User.TwoFactorEnabled = true; - await DbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(); await RefreshData(); } } @@ -239,12 +242,13 @@ else /// private async Task DisableTwoFactor() { - User = await DbContext.AliasVaultUsers.FindAsync(Id); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + User = await dbContext.AliasVaultUsers.FindAsync(Id); if (User != null) { User.TwoFactorEnabled = false; - await DbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(); await RefreshData(); } } @@ -256,16 +260,17 @@ else /// private async Task ResetTwoFactor() { - User = await DbContext.AliasVaultUsers.FindAsync(Id); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + User = await dbContext.AliasVaultUsers.FindAsync(Id); if (User != null) { // Remove all authenticator keys and recovery codes. - await DbContext.UserTokens + await dbContext.UserTokens .Where(x => x.UserId == User.Id && (x.Name == "AuthenticatorKey" || x.Name == "RecoveryCodes")) - .ForEachAsync(x => DbContext.UserTokens.Remove(x)); + .ForEachAsync(x => dbContext.UserTokens.Remove(x)); - await DbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(); await RefreshData(); } } @@ -276,6 +281,7 @@ else /// The vault to make current. private async Task MakeCurrentAsync(Vault vault) { + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); if (await ConfirmModalService.ShowConfirmation( title: "Confirm Vault Restoration", message: @"Are you sure you want to restore this specific vault and make it the active one? @@ -286,7 +292,7 @@ Important notes: Do you want to proceed with the restoration?")) { // Load vault - var currentVault = await DbContext.Vaults.FindAsync(vault.Id); + var currentVault = await dbContext.Vaults.FindAsync(vault.Id); if (currentVault == null) { return; @@ -296,7 +302,7 @@ Do you want to proceed with the restoration?")) { currentVault.RevisionNumber = VaultList.MaxBy(x => x.RevisionNumber)!.RevisionNumber + 1; // Save it. - await DbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(); // Reload the page. await RefreshData(); @@ -308,12 +314,13 @@ Do you want to proceed with the restoration?")) { /// private async Task ToggleBlockStatus() { - User = await DbContext.AliasVaultUsers.FindAsync(Id); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); + User = await dbContext.AliasVaultUsers.FindAsync(Id); if (User != null) { User.Blocked = !User.Blocked; - await DbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(); await RefreshData(); } } diff --git a/src/AliasVault.Admin/Services/UserService.cs b/src/AliasVault.Admin/Services/UserService.cs index 8436cb04..9dec2fad 100644 --- a/src/AliasVault.Admin/Services/UserService.cs +++ b/src/AliasVault.Admin/Services/UserService.cs @@ -15,10 +15,10 @@ namespace AliasVault.Admin.Services; /// /// User service for managing users. /// -/// AliasServerDbContext instance. +/// AliasServerDbContext instance. /// UserManager instance. /// HttpContextManager instance. -public class UserService(AliasServerDbContext dbContext, UserManager userManager, IHttpContextAccessor httpContextAccessor) +public class UserService(IAliasServerDbContextFactory dbContextFactory, UserManager userManager, IHttpContextAccessor httpContextAccessor) { private const string AdminRole = "Admin"; private AdminUser? _user; @@ -104,6 +104,7 @@ public async Task LoadCurrentUserAsync() // Load user from database. Use a new context everytime to ensure we get the latest data. var userName = httpContextAccessor.HttpContext?.User.Identity?.Name ?? string.Empty; + var dbContext = await dbContextFactory.CreateDbContextAsync(); var user = await dbContext.AdminUsers.FirstOrDefaultAsync(u => u.UserName == userName); if (user != null) { From 817404cd08dbcad6fb931fd485a71ec6ef75f9ae Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 22 Dec 2024 14:10:30 +0100 Subject: [PATCH 08/62] Refactor UserService delete unused methods causing concurrency issues (#190) --- src/AliasVault.Admin/Services/UserService.cs | 120 ------------------- 1 file changed, 120 deletions(-) diff --git a/src/AliasVault.Admin/Services/UserService.cs b/src/AliasVault.Admin/Services/UserService.cs index 9dec2fad..491f4134 100644 --- a/src/AliasVault.Admin/Services/UserService.cs +++ b/src/AliasVault.Admin/Services/UserService.cs @@ -23,16 +23,6 @@ public class UserService(IAliasServerDbContextFactory dbContextFactory, UserMana private const string AdminRole = "Admin"; private AdminUser? _user; - /// - /// The roles of the current user. - /// - private List _userRoles = []; - - /// - /// Whether the current user is an admin or not. - /// - private bool _isAdmin; - /// /// Allow other components to subscribe to changes in the event object. /// @@ -84,15 +74,6 @@ public AdminUser User() return _user; } - /// - /// Returns whether current user is admin or not. - /// - /// Boolean which indicates if user is admin. - public bool CurrentUserIsAdmin() - { - return _isAdmin; - } - /// /// Returns current logged on user based on HttpContext. /// @@ -109,13 +90,6 @@ public async Task LoadCurrentUserAsync() if (user != null) { _user = user; - - // Load all roles for current user. - var roles = await userManager.GetRolesAsync(User()); - _userRoles = roles.ToList(); - - // Define if current user is admin. - _isAdmin = _userRoles.Contains(AdminRole); } } @@ -123,58 +97,6 @@ public async Task LoadCurrentUserAsync() NotifyStateChanged(); } - /// - /// Returns current logged on user roles based on HttpContext. - /// - /// List of roles. - public async Task> GetCurrentUserRolesAsync() - { - var roles = await userManager.GetRolesAsync(User()); - - return roles.ToList(); - } - - /// - /// Search for users based on search term. - /// - /// Search term. - /// List of users matching the search term. - public async Task> SearchUsersAsync(string searchTerm) - { - return await userManager.Users.Where(x => x.UserName != null && x.UserName.Contains(searchTerm)).Take(5).ToListAsync(); - } - - /// - /// Create a new user. - /// - /// User object. - /// Password. - /// Roles. - /// List of errors if there are any. - public async Task> CreateUserAsync(AdminUser user, string password, List roles) - { - var errors = await ValidateUser(user, password, isUpdate: false); - if (errors.Count > 0) - { - return errors; - } - - var result = await userManager.CreateAsync(user, password); - if (!result.Succeeded) - { - foreach (var error in result.Errors) - { - errors.Add(error.Description); - } - - return errors; - } - - errors = await UpdateUserRolesAsync(user, roles); - - return errors; - } - /// /// Update user. /// @@ -229,48 +151,6 @@ public async Task> UpdateUserAsync(AdminUser user, string newPasswo return errors; } - /// - /// Checks if supplied password is correct for the user. - /// - /// User object. - /// The password to check. - /// Boolean indicating whether supplied password is valid and matches what is stored in the database. - public async Task CheckPasswordAsync(AdminUser user, string password) - { - if (password.Length == 0) - { - return false; - } - - return await userManager.CheckPasswordAsync(user, password); - } - - /// - /// Update user roles. This is a separate method because it is called from both CreateUserAsync and UpdateUserAsync. - /// - /// User object. - /// New roles for the user. - /// List of errors if any. - private async Task> UpdateUserRolesAsync(AdminUser user, List roles) - { - List errors = new(); - - var currentRoles = await userManager.GetRolesAsync(user); - if (user.Id == User().Id && currentRoles.Contains(AdminRole) && !roles.Contains(AdminRole)) - { - errors.Add("You cannot remove the Admin role from yourself if you are an Admin."); - return errors; - } - - var rolesToAdd = roles.Except(currentRoles).ToList(); - var rolesToRemove = currentRoles.Except(roles).ToList(); - - await userManager.AddToRolesAsync(user, rolesToAdd); - await userManager.RemoveFromRolesAsync(user, rolesToRemove); - - return errors; - } - /// /// Validate if user object contents conform to the requirements. /// From 1c53addcaab24e43345742d8d07ae6fbd5484979 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 22 Dec 2024 18:58:24 +0100 Subject: [PATCH 09/62] Refactor WebApi to use new dbcontextfactory (#190) --- src/AliasVault.Api/Controllers/AuthController.cs | 2 +- src/AliasVault.Api/Controllers/Email/EmailBoxController.cs | 2 +- src/AliasVault.Api/Controllers/Email/EmailController.cs | 2 +- src/AliasVault.Api/Controllers/RootController.cs | 2 +- src/AliasVault.Api/Controllers/Security/SecurityController.cs | 2 +- src/AliasVault.Api/Controllers/VaultController.cs | 2 +- src/AliasVault.Api/Program.cs | 4 ++-- src/AliasVault.Api/appsettings.json | 3 ++- 8 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/AliasVault.Api/Controllers/AuthController.cs b/src/AliasVault.Api/Controllers/AuthController.cs index 98e929c0..a3b7e905 100644 --- a/src/AliasVault.Api/Controllers/AuthController.cs +++ b/src/AliasVault.Api/Controllers/AuthController.cs @@ -43,7 +43,7 @@ namespace AliasVault.Api.Controllers; [Route("v{version:apiVersion}/[controller]")] [ApiController] [ApiVersion("1")] -public class AuthController(IDbContextFactory dbContextFactory, UserManager userManager, SignInManager signInManager, IConfiguration configuration, IMemoryCache cache, ITimeProvider timeProvider, AuthLoggingService authLoggingService, Config config) : ControllerBase +public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserManager userManager, SignInManager signInManager, IConfiguration configuration, IMemoryCache cache, ITimeProvider timeProvider, AuthLoggingService authLoggingService, Config config) : ControllerBase { /// /// Error message for invalid username or password. diff --git a/src/AliasVault.Api/Controllers/Email/EmailBoxController.cs b/src/AliasVault.Api/Controllers/Email/EmailBoxController.cs index 3464048a..ac4cdddc 100644 --- a/src/AliasVault.Api/Controllers/Email/EmailBoxController.cs +++ b/src/AliasVault.Api/Controllers/Email/EmailBoxController.cs @@ -23,7 +23,7 @@ namespace AliasVault.Api.Controllers.Email; /// DbContext instance. /// UserManager instance. [ApiVersion("1")] -public class EmailBoxController(IDbContextFactory dbContextFactory, UserManager userManager) : AuthenticatedRequestController(userManager) +public class EmailBoxController(IAliasServerDbContextFactory dbContextFactory, UserManager userManager) : AuthenticatedRequestController(userManager) { /// /// Returns a list of emails for the provided email address. diff --git a/src/AliasVault.Api/Controllers/Email/EmailController.cs b/src/AliasVault.Api/Controllers/Email/EmailController.cs index 7ba4c518..a2779135 100644 --- a/src/AliasVault.Api/Controllers/Email/EmailController.cs +++ b/src/AliasVault.Api/Controllers/Email/EmailController.cs @@ -22,7 +22,7 @@ namespace AliasVault.Api.Controllers.Email; /// DbContext instance. /// UserManager instance. [ApiVersion("1")] -public class EmailController(ILogger logger, IDbContextFactory dbContextFactory, UserManager userManager) : AuthenticatedRequestController(userManager) +public class EmailController(ILogger logger, IAliasServerDbContextFactory dbContextFactory, UserManager userManager) : AuthenticatedRequestController(userManager) { /// /// Get the newest version of the vault for the current user. diff --git a/src/AliasVault.Api/Controllers/RootController.cs b/src/AliasVault.Api/Controllers/RootController.cs index 2244698c..9e51cb59 100644 --- a/src/AliasVault.Api/Controllers/RootController.cs +++ b/src/AliasVault.Api/Controllers/RootController.cs @@ -16,7 +16,7 @@ namespace AliasVault.Api.Controllers; /// [ApiController] [Route("/")] -public class RootController(IDbContextFactory dbContextFactory) : ControllerBase +public class RootController(IAliasServerDbContextFactory dbContextFactory) : ControllerBase { /// /// Root endpoint that returns a 200 OK if the database connection is successful diff --git a/src/AliasVault.Api/Controllers/Security/SecurityController.cs b/src/AliasVault.Api/Controllers/Security/SecurityController.cs index c0729f36..7bcc9194 100644 --- a/src/AliasVault.Api/Controllers/Security/SecurityController.cs +++ b/src/AliasVault.Api/Controllers/Security/SecurityController.cs @@ -24,7 +24,7 @@ namespace AliasVault.Api.Controllers.Security; [Route("v{version:apiVersion}/[controller]")] [ApiController] [ApiVersion("1")] -public class SecurityController(IDbContextFactory dbContextFactory, UserManager userManager) : AuthenticatedRequestController(userManager) +public class SecurityController(IAliasServerDbContextFactory dbContextFactory, UserManager userManager) : AuthenticatedRequestController(userManager) { /// /// Returns list of active sessions (refresh tokens) for the current user. diff --git a/src/AliasVault.Api/Controllers/VaultController.cs b/src/AliasVault.Api/Controllers/VaultController.cs index 6b0a5993..58f9877e 100644 --- a/src/AliasVault.Api/Controllers/VaultController.cs +++ b/src/AliasVault.Api/Controllers/VaultController.cs @@ -36,7 +36,7 @@ namespace AliasVault.Api.Controllers; /// AuthLoggingService instance. /// IMemoryCache instance. [ApiVersion("1")] -public class VaultController(ILogger logger, IDbContextFactory dbContextFactory, UserManager userManager, ITimeProvider timeProvider, AuthLoggingService authLoggingService, IMemoryCache cache) : AuthenticatedRequestController(userManager) +public class VaultController(ILogger logger, IAliasServerDbContextFactory dbContextFactory, UserManager userManager, ITimeProvider timeProvider, AuthLoggingService authLoggingService, IMemoryCache cache) : AuthenticatedRequestController(userManager) { /// /// Error message for providing an invalid current password (during password change). diff --git a/src/AliasVault.Api/Program.cs b/src/AliasVault.Api/Program.cs index 95b3c69e..658e3393 100644 --- a/src/AliasVault.Api/Program.cs +++ b/src/AliasVault.Api/Program.cs @@ -48,7 +48,7 @@ logging.AddFilter("Microsoft.AspNetCore.Identity.UserManager", LogLevel.Error); }); -builder.Services.AddAliasVaultSqliteConfiguration(); +builder.Services.AddAliasVaultDatabaseConfiguration(builder.Configuration); builder.Services.AddIdentity(options => { options.Password.RequireDigit = false; @@ -178,7 +178,7 @@ using (var scope = app.Services.CreateScope()) { var container = scope.ServiceProvider; - await using var db = await container.GetRequiredService>().CreateDbContextAsync(); + await using var db = await container.GetRequiredService().CreateDbContextAsync(); await db.Database.MigrateAsync(); } diff --git a/src/AliasVault.Api/appsettings.json b/src/AliasVault.Api/appsettings.json index 49f82d93..0a484e5f 100644 --- a/src/AliasVault.Api/appsettings.json +++ b/src/AliasVault.Api/appsettings.json @@ -11,8 +11,9 @@ "Jwt": { "Issuer": "AliasVault" }, + "DatabaseProvider": "postgresql", "ConnectionStrings": { - "AliasServerDbContext": "Data Source=../../database/AliasServerDb.sqlite" + "AliasServerDbContext": "Host=localhost;Port=5432;Database=aliasvault;Username=aliasvault;Password=password" }, "AllowedHosts": "*" } From db632c3edbe0ba8c06503273a9f2ea38469b600b Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 22 Dec 2024 20:05:05 +0100 Subject: [PATCH 10/62] Refactor SmtpService to use new dbcontextfactory (#190) --- docs/misc/dev/upgrade-ef-server-model.md | 22 +++++++++++++++++++ .../WorkerStatus/ServiceControl.razor | 6 ++--- .../Configuration/DatabaseConfiguration.cs | 13 +++++++++-- .../IAliasServerDbContextFactory.cs | 8 +++++++ ...241222185633_InitialMigration.Designer.cs} | 4 ++-- ....cs => 20241222185633_InitialMigration.cs} | 2 +- ...sServerDbContextPostgresqlModelSnapshot.cs | 2 +- .../PostgresqlDbContextFactory.cs | 16 +++++++++----- .../AliasServerDb/SqliteDbContextFactory.cs | 9 ++++++-- .../Handlers/DatabaseMessageStore.cs | 2 +- .../AliasVault.SmtpService/Program.cs | 4 ++-- .../AliasVault.SmtpService/appsettings.json | 3 ++- src/Services/AliasVault.TaskRunner/Program.cs | 4 ++-- .../Tasks/EmailCleanupTask.cs | 4 ++-- .../Tasks/EmailQuotaCleanupTask.cs | 4 ++-- .../Tasks/LogCleanupTask.cs | 4 ++-- .../Tasks/RefreshTokenCleanupTask.cs | 4 ++-- .../Workers/TaskRunnerWorker.cs | 2 +- .../AliasVault.TaskRunner/appsettings.json | 3 ++- .../Database/WorkerServiceStatus.cs | 1 - .../ServiceCollectionExtensions.cs | 2 +- .../AliasVault.WorkerStatus/StatusWorker.cs | 4 ++-- 22 files changed, 87 insertions(+), 36 deletions(-) create mode 100644 docs/misc/dev/upgrade-ef-server-model.md rename src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/{20241222101415_InitialMigration.Designer.cs => 20241222185633_InitialMigration.Designer.cs} (99%) rename src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/{20241222101415_InitialMigration.cs => 20241222185633_InitialMigration.cs} (99%) diff --git a/docs/misc/dev/upgrade-ef-server-model.md b/docs/misc/dev/upgrade-ef-server-model.md new file mode 100644 index 00000000..80415a50 --- /dev/null +++ b/docs/misc/dev/upgrade-ef-server-model.md @@ -0,0 +1,22 @@ +--- +layout: default +title: Upgrade the AliasClientDb EF model +parent: Development +grand_parent: Miscellaneous +nav_order: 3 +--- + +# Upgrade the AliasServerDb EF model + +The AliasServerDb EF model has migrations for both the SQLite and PostgreSQL databases. This means +that when you make changes to the EF model, you need to create migrations for both databases. + +1. Make migration for PostgreSQL database: +```bash +dotnet ef migrations add InitialMigration --context AliasServerDbContextPostgresql --output-dir Migrations/PostgresqlMigrations +``` + +2. Make migration for SQLite database: +```bash +dotnet ef migrations add InitialMigration --context AliasServerDbContextSqlite --output-dir Migrations/SqliteMigrations +``` diff --git a/src/AliasVault.Admin/Main/Components/WorkerStatus/ServiceControl.razor b/src/AliasVault.Admin/Main/Components/WorkerStatus/ServiceControl.razor index 1794f68f..e4833d0b 100644 --- a/src/AliasVault.Admin/Main/Components/WorkerStatus/ServiceControl.razor +++ b/src/AliasVault.Admin/Main/Components/WorkerStatus/ServiceControl.razor @@ -102,7 +102,7 @@ /// private static bool IsHeartbeatValid(DateTime lastHeartbeat) { - return DateTime.Now <= lastHeartbeat.AddMinutes(5); + return DateTime.UtcNow <= lastHeartbeat.AddMinutes(5); } /// @@ -205,10 +205,10 @@ entry.DesiredStatus = newDesiredStatus; await dbContext.SaveChangesAsync(); - var timeout = DateTime.Now.AddSeconds(30); + var timeout = DateTime.UtcNow.AddSeconds(30); while (true) { - if (DateTime.Now > timeout) + if (DateTime.UtcNow > timeout) { return false; } diff --git a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs index 2d4a67b7..e68b75a9 100644 --- a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs +++ b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs @@ -25,17 +25,26 @@ public static IServiceCollection AddAliasVaultDatabaseConfiguration(this IServic { var dbProvider = configuration.GetValue("DatabaseProvider")?.ToLower() ?? "sqlite"; + // Add custom DbContextFactory registration which supports multiple database providers. switch (dbProvider) { case "postgresql": - services.AddScoped(); + services.AddSingleton(); break; case "sqlite": default: - services.AddScoped(); + services.AddSingleton(); break; } + // Updated DbContextFactory registration + services.AddDbContextFactory((sp, options) => + { + var factory = sp.GetRequiredService(); + factory.ConfigureDbContextOptions(options); // Let the factory configure the options directly + }); + + // Add scoped DbContext registration based on the factory services.AddScoped(sp => { var factory = sp.GetRequiredService(); diff --git a/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs b/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs index 3b19af1c..126c4a16 100644 --- a/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs +++ b/src/Databases/AliasServerDb/IAliasServerDbContextFactory.cs @@ -7,6 +7,8 @@ namespace AliasServerDb; +using Microsoft.EntityFrameworkCore; + /// /// The AliasServerDbContextFactory interface. /// @@ -18,6 +20,12 @@ public interface IAliasServerDbContextFactory /// The AliasServerDbContext. AliasServerDbContext CreateDbContext(); + /// + /// Configures the DbContext options. + /// + /// The DbContextOptionsBuilder. + void ConfigureDbContextOptions(DbContextOptionsBuilder optionsBuilder); + /// /// Creates a new AliasServerDbContext asynchronously. /// diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.Designer.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.Designer.cs rename to src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.Designer.cs index 9ebe53de..a916fe20 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.Designer.cs @@ -12,7 +12,7 @@ namespace AliasServerDb.Migrations.PostgresqlMigrations { [DbContext(typeof(AliasServerDbContextPostgresql))] - [Migration("20241222101415_InitialMigration")] + [Migration("20241222185633_InitialMigration")] partial class InitialMigration { /// @@ -699,7 +699,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ServiceName") .IsRequired() .HasMaxLength(255) - .HasColumnType("varchar"); + .HasColumnType("character varying(255)"); b.HasKey("Id"); diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.cs rename to src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.cs index cf7dabf8..38900340 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222101415_InitialMigration.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.cs @@ -265,7 +265,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ServiceName = table.Column(type: "varchar", maxLength: 255, nullable: false), + ServiceName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), CurrentStatus = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), DesiredStatus = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), Heartbeat = table.Column(type: "timestamp with time zone", nullable: false) diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs index efe471f3..c0d63ab9 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs @@ -696,7 +696,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ServiceName") .IsRequired() .HasMaxLength(255) - .HasColumnType("varchar"); + .HasColumnType("character varying(255)"); b.HasKey("Id"); diff --git a/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs index 262d040d..0d5bb6e1 100644 --- a/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs +++ b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs @@ -30,11 +30,7 @@ public PostgresqlDbContextFactory(IConfiguration configuration) public AliasServerDbContext CreateDbContext() { var optionsBuilder = new DbContextOptionsBuilder(); - var connectionString = _configuration.GetConnectionString("AliasServerDbContext"); - - optionsBuilder - .UseNpgsql(connectionString, options => options.CommandTimeout(60)) - .UseLazyLoadingProxies(); + ConfigureDbContextOptions(optionsBuilder); return new AliasServerDbContextPostgresql(optionsBuilder.Options); } @@ -44,4 +40,14 @@ public Task CreateDbContextAsync(CancellationToken cancell { return Task.FromResult(CreateDbContext()); } + + /// + public void ConfigureDbContextOptions(DbContextOptionsBuilder optionsBuilder) + { + var connectionString = _configuration.GetConnectionString("AliasServerDbContext"); + + optionsBuilder + .UseNpgsql(connectionString, options => options.CommandTimeout(60)) + .UseLazyLoadingProxies(); + } } diff --git a/src/Databases/AliasServerDb/SqliteDbContextFactory.cs b/src/Databases/AliasServerDb/SqliteDbContextFactory.cs index 69f08b50..e20f803e 100644 --- a/src/Databases/AliasServerDb/SqliteDbContextFactory.cs +++ b/src/Databases/AliasServerDb/SqliteDbContextFactory.cs @@ -27,16 +27,21 @@ public SqliteDbContextFactory(IConfiguration configuration) } /// - public AliasServerDbContext CreateDbContext() + public void ConfigureDbContextOptions(DbContextOptionsBuilder optionsBuilder) { - var optionsBuilder = new DbContextOptionsBuilder(); var connectionString = _configuration.GetConnectionString("AliasServerDbContext") + ";Mode=ReadWriteCreate;Cache=Shared"; optionsBuilder .UseSqlite(connectionString, options => options.CommandTimeout(60)) .UseLazyLoadingProxies(); + } + /// + public AliasServerDbContext CreateDbContext() + { + var optionsBuilder = new DbContextOptionsBuilder(); + ConfigureDbContextOptions(optionsBuilder); return new AliasServerDbContextSqlite(optionsBuilder.Options); } diff --git a/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs b/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs index ec420b07..e397f5f4 100644 --- a/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs +++ b/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs @@ -26,7 +26,7 @@ namespace AliasVault.SmtpService.Handlers; /// ILogger instance. /// Config instance. /// IDbContextFactory instance. -public class DatabaseMessageStore(ILogger logger, Config config, IDbContextFactory dbContextFactory) : MessageStore +public class DatabaseMessageStore(ILogger logger, Config config, IAliasServerDbContextFactory dbContextFactory) : MessageStore { /// /// Override the SaveAsync method to save the email into the database. diff --git a/src/Services/AliasVault.SmtpService/Program.cs b/src/Services/AliasVault.SmtpService/Program.cs index 36801abb..73dde0e5 100644 --- a/src/Services/AliasVault.SmtpService/Program.cs +++ b/src/Services/AliasVault.SmtpService/Program.cs @@ -34,7 +34,7 @@ config.SmtpTlsEnabled = tlsEnabled; builder.Services.AddSingleton(config); -builder.Services.AddAliasVaultSqliteConfiguration(); +builder.Services.AddAliasVaultDatabaseConfiguration(builder.Configuration); builder.Services.AddTransient(); builder.Services.AddSingleton( provider => @@ -112,7 +112,7 @@ static X509Certificate2 CreateCertificate() using (var scope = host.Services.CreateScope()) { var container = scope.ServiceProvider; - var factory = container.GetRequiredService>(); + var factory = container.GetRequiredService(); await using var context = await factory.CreateDbContextAsync(); await context.Database.MigrateAsync(); } diff --git a/src/Services/AliasVault.SmtpService/appsettings.json b/src/Services/AliasVault.SmtpService/appsettings.json index ddbf1678..e8623fc1 100644 --- a/src/Services/AliasVault.SmtpService/appsettings.json +++ b/src/Services/AliasVault.SmtpService/appsettings.json @@ -6,7 +6,8 @@ "Microsoft.EntityFrameworkCore": "Warning" } }, + "DatabaseProvider": "postgresql", "ConnectionStrings": { - "AliasServerDbContext": "Data Source=../../../database/AliasServerDb.sqlite" + "AliasServerDbContext": "Host=localhost;Port=5432;Database=aliasvault;Username=aliasvault;Password=password" } } diff --git a/src/Services/AliasVault.TaskRunner/Program.cs b/src/Services/AliasVault.TaskRunner/Program.cs index 0e73ec97..99cee1e3 100644 --- a/src/Services/AliasVault.TaskRunner/Program.cs +++ b/src/Services/AliasVault.TaskRunner/Program.cs @@ -20,7 +20,7 @@ builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true); builder.Services.ConfigureLogging(builder.Configuration, Assembly.GetExecutingAssembly().GetName().Name!, "../../../logs"); -builder.Services.AddAliasVaultSqliteConfiguration(); +builder.Services.AddAliasVaultDatabaseConfiguration(builder.Configuration); // ----------------------------------------------------------------------- // Register hosted services via Status library wrapper in order to monitor and control (start/stop) them via the database. @@ -40,7 +40,7 @@ using (var scope = host.Services.CreateScope()) { var container = scope.ServiceProvider; - var factory = container.GetRequiredService>(); + var factory = container.GetRequiredService(); await using var context = await factory.CreateDbContextAsync(); await context.Database.MigrateAsync(); } diff --git a/src/Services/AliasVault.TaskRunner/Tasks/EmailCleanupTask.cs b/src/Services/AliasVault.TaskRunner/Tasks/EmailCleanupTask.cs index d08c300d..0a62dc4e 100644 --- a/src/Services/AliasVault.TaskRunner/Tasks/EmailCleanupTask.cs +++ b/src/Services/AliasVault.TaskRunner/Tasks/EmailCleanupTask.cs @@ -17,7 +17,7 @@ namespace AliasVault.TaskRunner.Tasks; public class EmailCleanupTask : IMaintenanceTask { private readonly ILogger _logger; - private readonly IDbContextFactory _dbContextFactory; + private readonly IAliasServerDbContextFactory _dbContextFactory; private readonly ServerSettingsService _settingsService; /// @@ -28,7 +28,7 @@ public class EmailCleanupTask : IMaintenanceTask /// The settings service. public EmailCleanupTask( ILogger logger, - IDbContextFactory dbContextFactory, + IAliasServerDbContextFactory dbContextFactory, ServerSettingsService settingsService) { _logger = logger; diff --git a/src/Services/AliasVault.TaskRunner/Tasks/EmailQuotaCleanupTask.cs b/src/Services/AliasVault.TaskRunner/Tasks/EmailQuotaCleanupTask.cs index 28d67d87..56a5c5d1 100644 --- a/src/Services/AliasVault.TaskRunner/Tasks/EmailQuotaCleanupTask.cs +++ b/src/Services/AliasVault.TaskRunner/Tasks/EmailQuotaCleanupTask.cs @@ -17,7 +17,7 @@ namespace AliasVault.TaskRunner.Tasks; public class EmailQuotaCleanupTask : IMaintenanceTask { private readonly ILogger _logger; - private readonly IDbContextFactory _dbContextFactory; + private readonly IAliasServerDbContextFactory _dbContextFactory; private readonly ServerSettingsService _settingsService; /// @@ -28,7 +28,7 @@ public class EmailQuotaCleanupTask : IMaintenanceTask /// The settings service. public EmailQuotaCleanupTask( ILogger logger, - IDbContextFactory dbContextFactory, + IAliasServerDbContextFactory dbContextFactory, ServerSettingsService settingsService) { _logger = logger; diff --git a/src/Services/AliasVault.TaskRunner/Tasks/LogCleanupTask.cs b/src/Services/AliasVault.TaskRunner/Tasks/LogCleanupTask.cs index c22602e3..dc1929ad 100644 --- a/src/Services/AliasVault.TaskRunner/Tasks/LogCleanupTask.cs +++ b/src/Services/AliasVault.TaskRunner/Tasks/LogCleanupTask.cs @@ -17,7 +17,7 @@ namespace AliasVault.TaskRunner.Tasks; public class LogCleanupTask : IMaintenanceTask { private readonly ILogger _logger; - private readonly IDbContextFactory _dbContextFactory; + private readonly IAliasServerDbContextFactory _dbContextFactory; private readonly ServerSettingsService _settingsService; /// @@ -28,7 +28,7 @@ public class LogCleanupTask : IMaintenanceTask /// The settings service. public LogCleanupTask( ILogger logger, - IDbContextFactory dbContextFactory, + IAliasServerDbContextFactory dbContextFactory, ServerSettingsService settingsService) { _logger = logger; diff --git a/src/Services/AliasVault.TaskRunner/Tasks/RefreshTokenCleanupTask.cs b/src/Services/AliasVault.TaskRunner/Tasks/RefreshTokenCleanupTask.cs index 06a8cc2e..baf1450e 100644 --- a/src/Services/AliasVault.TaskRunner/Tasks/RefreshTokenCleanupTask.cs +++ b/src/Services/AliasVault.TaskRunner/Tasks/RefreshTokenCleanupTask.cs @@ -16,7 +16,7 @@ namespace AliasVault.TaskRunner.Tasks; public class RefreshTokenCleanupTask : IMaintenanceTask { private readonly ILogger _logger; - private readonly IDbContextFactory _dbContextFactory; + private readonly IAliasServerDbContextFactory _dbContextFactory; /// /// Initializes a new instance of the class. @@ -25,7 +25,7 @@ public class RefreshTokenCleanupTask : IMaintenanceTask /// The database context factory. public RefreshTokenCleanupTask( ILogger logger, - IDbContextFactory dbContextFactory) + IAliasServerDbContextFactory dbContextFactory) { _logger = logger; _dbContextFactory = dbContextFactory; diff --git a/src/Services/AliasVault.TaskRunner/Workers/TaskRunnerWorker.cs b/src/Services/AliasVault.TaskRunner/Workers/TaskRunnerWorker.cs index dde5d7fd..75fde18e 100644 --- a/src/Services/AliasVault.TaskRunner/Workers/TaskRunnerWorker.cs +++ b/src/Services/AliasVault.TaskRunner/Workers/TaskRunnerWorker.cs @@ -24,7 +24,7 @@ public class TaskRunnerWorker( ILogger logger, IEnumerable tasks, ServerSettingsService settingsService, - IDbContextFactory dbContextFactory) : BackgroundService + IAliasServerDbContextFactory dbContextFactory) : BackgroundService { /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) diff --git a/src/Services/AliasVault.TaskRunner/appsettings.json b/src/Services/AliasVault.TaskRunner/appsettings.json index ddbf1678..e8623fc1 100644 --- a/src/Services/AliasVault.TaskRunner/appsettings.json +++ b/src/Services/AliasVault.TaskRunner/appsettings.json @@ -6,7 +6,8 @@ "Microsoft.EntityFrameworkCore": "Warning" } }, + "DatabaseProvider": "postgresql", "ConnectionStrings": { - "AliasServerDbContext": "Data Source=../../../database/AliasServerDb.sqlite" + "AliasServerDbContext": "Host=localhost;Port=5432;Database=aliasvault;Username=aliasvault;Password=password" } } diff --git a/src/Utilities/AliasVault.WorkerStatus/Database/WorkerServiceStatus.cs b/src/Utilities/AliasVault.WorkerStatus/Database/WorkerServiceStatus.cs index 349ac2ef..8df1b2e7 100644 --- a/src/Utilities/AliasVault.WorkerStatus/Database/WorkerServiceStatus.cs +++ b/src/Utilities/AliasVault.WorkerStatus/Database/WorkerServiceStatus.cs @@ -25,7 +25,6 @@ public class WorkerServiceStatus /// [Required] [StringLength(255)] - [Column(TypeName = "varchar")] public string ServiceName { get; set; } = null!; /// diff --git a/src/Utilities/AliasVault.WorkerStatus/ServiceExtensions/ServiceCollectionExtensions.cs b/src/Utilities/AliasVault.WorkerStatus/ServiceExtensions/ServiceCollectionExtensions.cs index 1d347c52..7e6def75 100644 --- a/src/Utilities/AliasVault.WorkerStatus/ServiceExtensions/ServiceCollectionExtensions.cs +++ b/src/Utilities/AliasVault.WorkerStatus/ServiceExtensions/ServiceCollectionExtensions.cs @@ -31,7 +31,7 @@ public static IServiceCollection AddStatusHostedService(this where TWorker : class, IHostedService where TContext : DbContext, IWorkerStatusDbContext { - services.TryAddSingleton(); // Register the inner service + services.TryAddSingleton(); services.TryAddEnumerable(ServiceDescriptor.Singleton>()); // Only add these required helper services if they are not already registered. diff --git a/src/Utilities/AliasVault.WorkerStatus/StatusWorker.cs b/src/Utilities/AliasVault.WorkerStatus/StatusWorker.cs index 08255a37..a7ad49d1 100644 --- a/src/Utilities/AliasVault.WorkerStatus/StatusWorker.cs +++ b/src/Utilities/AliasVault.WorkerStatus/StatusWorker.cs @@ -99,7 +99,7 @@ private async Task GetServiceStatus() globalServiceStatus.Status = entry.CurrentStatus; globalServiceStatus.CurrentStatus = entry.CurrentStatus; - entry.Heartbeat = DateTime.Now; + entry.Heartbeat = DateTime.UtcNow; await _dbContext.SaveChangesAsync(); return entry; @@ -122,7 +122,7 @@ private async Task SetServiceStatus(WorkerServiceStatus statusEntry, string newS globalServiceStatus.Status = status; globalServiceStatus.CurrentStatus = status; - statusEntry.Heartbeat = DateTime.Now; + statusEntry.Heartbeat = DateTime.UtcNow; await _dbContext.SaveChangesAsync(); } From 22538ae00022fbe9dfac0b8d8c1c5f4e115c9330 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sun, 22 Dec 2024 21:36:01 +0100 Subject: [PATCH 11/62] Refactor datetime to always use UTC (#190) --- src/AliasVault.Admin/Main/Pages/Settings/Server.razor | 4 ++-- src/AliasVault.Client/Main/Components/Email/EmailModal.razor | 4 ++-- .../AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/AliasVault.Admin/Main/Pages/Settings/Server.razor b/src/AliasVault.Admin/Main/Pages/Settings/Server.razor index d9ed32d2..1c0676f6 100644 --- a/src/AliasVault.Admin/Main/Pages/Settings/Server.razor +++ b/src/AliasVault.Admin/Main/Pages/Settings/Server.razor @@ -126,8 +126,8 @@ var job = new TaskRunnerJob { Name = nameof(TaskRunnerJobType.Maintenance), - RunDate = DateTime.Now.Date, - StartTime = TimeOnly.FromDateTime(DateTime.Now), + RunDate = DateTime.UtcNow.Date, + StartTime = TimeOnly.FromDateTime(DateTime.UtcNow), Status = TaskRunnerJobStatus.Pending, IsOnDemand = true }; diff --git a/src/AliasVault.Client/Main/Components/Email/EmailModal.razor b/src/AliasVault.Client/Main/Components/Email/EmailModal.razor index d514cc88..964af599 100644 --- a/src/AliasVault.Client/Main/Components/Email/EmailModal.razor +++ b/src/AliasVault.Client/Main/Components/Email/EmailModal.razor @@ -119,8 +119,8 @@ var response = await client.DeleteAsync($"https://api.spamok.com/v2/Email/{Email.ToLocal}/{Email.Id}"); if (response.IsSuccessStatusCode) { - GlobalNotificationService.AddSuccessMessage("Email deleted successfully", true); await OnEmailDeleted.InvokeAsync(Email.Id); + GlobalNotificationService.AddSuccessMessage("Email deleted successfully", true); await Close(); } else @@ -150,8 +150,8 @@ var response = await HttpClient.DeleteAsync($"v1/Email/{Email.Id}"); if (response.IsSuccessStatusCode) { - GlobalNotificationService.AddSuccessMessage("Email deleted successfully", true); await OnEmailDeleted.InvokeAsync(Email.Id); + GlobalNotificationService.AddSuccessMessage("Email deleted successfully", true); await Close(); } else diff --git a/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs b/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs index e397f5f4..106b5da8 100644 --- a/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs +++ b/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs @@ -156,7 +156,7 @@ private static Email ConvertMimeMessageToEmail(MimeMessage message, MailAddress MessageHtml = message.HtmlBody, MessagePlain = message.TextBody, MessageSource = message.ToString(), - Date = message.Date.DateTime, + Date = message.Date.DateTime.ToUniversalTime(), DateSystem = DateTime.UtcNow, Visible = true, }; @@ -254,7 +254,7 @@ private static EmailAttachment CreateEmailAttachment(MimeEntity attachment) Filename = attachment.ContentDisposition?.FileName ?? string.Empty, MimeType = attachment.ContentType.MimeType, Filesize = fileBytes.Length, - Date = DateTime.Now, + Date = DateTime.UtcNow, }; } From 77a48ea4e98e47b88fdd819535eebd35c0c029cc Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 12:16:05 +0100 Subject: [PATCH 12/62] Refactor admin so all tests pass (#190) --- src/AliasVault.Admin/Auth/Pages/Logout.razor | 9 ++- .../WorkerStatus/ServiceControl.razor | 8 +- .../Pages/Account/Manage/ChangePassword.razor | 25 +++--- .../Pages/Account/Manage/Disable2fa.razor | 16 +++- .../Account/Manage/EnableAuthenticator.razor | 50 ++++++++---- .../Manage/GenerateRecoveryCodes.razor | 25 ++++-- .../Account/Manage/ResetAuthenticator.razor | 13 ++- .../Manage/TwoFactorAuthentication.razor | 30 ++++--- src/AliasVault.Admin/Main/Pages/MainBase.cs | 9 --- .../Components/TaskRunnerHistory.razor | 2 +- .../Main/Pages/Settings/Server.razor | 2 +- src/AliasVault.Admin/Services/UserService.cs | 8 +- .../WebApplicationAdminFactoryFixture.cs | 80 +++++++++++++----- .../WebApplicationApiFactoryFixture.cs | 81 ++++++++++++++----- .../Tests/Admin/ServerSettingsTests.cs | 5 +- .../Tests/Admin/TwoFactorAuthLockoutTests.cs | 3 + 16 files changed, 247 insertions(+), 119 deletions(-) diff --git a/src/AliasVault.Admin/Auth/Pages/Logout.razor b/src/AliasVault.Admin/Auth/Pages/Logout.razor index 3fc33fdb..abaa3306 100644 --- a/src/AliasVault.Admin/Auth/Pages/Logout.razor +++ b/src/AliasVault.Admin/Auth/Pages/Logout.razor @@ -8,11 +8,13 @@ protected override async Task OnInitializedAsync() { // Sign out the user. - // NOTE: the try/catch below is a workaround for the issue that the sign out does not work when + // NOTE: the try/catch below is a workaround for the issue that the sign-out does not work when // the server session is already started. try { + await UserService.LoadCurrentUserAsync(); var username = UserService.User().UserName; + try { await SignInManager.SignOutAsync(); @@ -22,11 +24,12 @@ // Redirect to the home page with hard refresh. NavigationService.RedirectTo("/", true); } - catch + catch (Exception ex) { // Hard refresh current page if sign out fails. When an interactive server session is already started - // the sign out will fail because it tries to mutate cookies which is only possible when the server + // the sign-out will fail because it tries to mutate cookies which is only possible when the server // session is not started yet. + Console.WriteLine(ex); await AuthLoggingService.LogAuthEventSuccessAsync(username!, AuthEventType.Logout); NavigationService.RedirectTo(NavigationService.Uri, true); } diff --git a/src/AliasVault.Admin/Main/Components/WorkerStatus/ServiceControl.razor b/src/AliasVault.Admin/Main/Components/WorkerStatus/ServiceControl.razor index e4833d0b..55ed17aa 100644 --- a/src/AliasVault.Admin/Main/Components/WorkerStatus/ServiceControl.razor +++ b/src/AliasVault.Admin/Main/Components/WorkerStatus/ServiceControl.razor @@ -171,7 +171,7 @@ try { InitInProgress = true; - var dbContext = await DbContextFactory.CreateDbContextAsync(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); ServiceStatus = await dbContext.WorkerServiceStatuses.ToListAsync(); foreach (var service in Services) @@ -197,7 +197,7 @@ /// private async Task UpdateServiceStatus(string serviceName, bool newStatus) { - var dbContext = await DbContextFactory.CreateDbContextAsync(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); var entry = await dbContext.WorkerServiceStatuses.Where(x => x.ServiceName == serviceName).FirstOrDefaultAsync(); if (entry != null) { @@ -213,8 +213,8 @@ return false; } - dbContext = await DbContextFactory.CreateDbContextAsync(); - var check = await dbContext.WorkerServiceStatuses.Where(x => x.ServiceName == serviceName).FirstAsync(); + await using var dbContextInner = await DbContextFactory.CreateDbContextAsync(); + var check = await dbContextInner.WorkerServiceStatuses.Where(x => x.ServiceName == serviceName).FirstAsync(); if (check.CurrentStatus == newDesiredStatus) { return true; diff --git a/src/AliasVault.Admin/Main/Pages/Account/Manage/ChangePassword.razor b/src/AliasVault.Admin/Main/Pages/Account/Manage/ChangePassword.razor index 8aa519f6..90cb9345 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/Manage/ChangePassword.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/Manage/ChangePassword.razor @@ -2,7 +2,6 @@ @using System.ComponentModel.DataAnnotations @using Microsoft.AspNetCore.Identity - @inject UserManager UserManager @inject ILogger Logger @@ -41,15 +40,13 @@ private async Task OnValidSubmitAsync() { - var changePasswordResult = await UserManager.ChangePasswordAsync(UserService.User(), Input.OldPassword, Input.NewPassword); - var user = UserService.User(); - user.LastPasswordChanged = DateTime.UtcNow; - await UserService.UpdateUserAsync(user); + var user = await UserManager.FindByIdAsync(UserService.User().Id); + if (user == null) + { + throw new InvalidOperationException("User not found."); + } - // Clear the password fields - Input.OldPassword = ""; - Input.NewPassword = ""; - Input.ConfirmPassword = ""; + var changePasswordResult = await UserManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); if (!changePasswordResult.Succeeded) { @@ -57,10 +54,15 @@ return; } - Logger.LogInformation("User changed their password successfully."); + user.LastPasswordChanged = DateTime.UtcNow; + await UserManager.UpdateAsync(user); - GlobalNotificationService.AddSuccessMessage("Your password has been changed."); + Input.OldPassword = ""; + Input.NewPassword = ""; + Input.ConfirmPassword = ""; + Logger.LogInformation("User changed their password successfully."); + GlobalNotificationService.AddSuccessMessage("Your password has been changed."); NavigationService.RedirectToCurrentPage(); } @@ -82,5 +84,4 @@ [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] public string ConfirmPassword { get; set; } = ""; } - } diff --git a/src/AliasVault.Admin/Main/Pages/Account/Manage/Disable2fa.razor b/src/AliasVault.Admin/Main/Pages/Account/Manage/Disable2fa.razor index 8eb7cb96..c06a4fb3 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/Manage/Disable2fa.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/Manage/Disable2fa.razor @@ -31,7 +31,13 @@ /// protected override async Task OnInitializedAsync() { - if (!await UserManager.GetTwoFactorEnabledAsync(UserService.User())) + var user = await UserManager.FindByIdAsync(UserService.User().Id); + if (user == null) + { + throw new InvalidOperationException("User not found."); + } + + if (!await UserManager.GetTwoFactorEnabledAsync(user)) { throw new InvalidOperationException("Cannot disable 2FA for user as it's not currently enabled."); } @@ -39,7 +45,13 @@ private async Task OnSubmitAsync() { - var disable2FaResult = await UserManager.SetTwoFactorEnabledAsync(UserService.User(), false); + var user = await UserManager.FindByIdAsync(UserService.User().Id); + if (user == null) + { + throw new InvalidOperationException("User not found."); + } + + var disable2FaResult = await UserManager.SetTwoFactorEnabledAsync(user, false); if (!disable2FaResult.Succeeded) { await AuthLoggingService.LogAuthEventFailAsync(UserService.User().UserName!, AuthEventType.TwoFactorAuthDisable, AuthFailureReason.Unknown); diff --git a/src/AliasVault.Admin/Main/Pages/Account/Manage/EnableAuthenticator.razor b/src/AliasVault.Admin/Main/Pages/Account/Manage/EnableAuthenticator.razor index e2f3c539..f322c41c 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/Manage/EnableAuthenticator.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/Manage/EnableAuthenticator.razor @@ -13,6 +13,12 @@ Configure authenticator app +@if (_isLoading) +{ + + return; +} + @if (RecoveryCodes is not null) { @@ -69,15 +75,20 @@ else private string? SharedKey { get; set; } private string? AuthenticatorUri { get; set; } private IEnumerable? RecoveryCodes { get; set; } + private bool _isLoading = true; [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); - /// - protected override async Task OnInitializedAsync() + /// + protected override async Task OnAfterRenderAsync(bool firstRender) { - await base.OnInitializedAsync(); - await LoadSharedKeyAndQrCodeUriAsync(UserService.User()); - await JsInvokeService.RetryInvokeAsync("generateQrCode", TimeSpan.Zero, 5, "authenticator-uri"); + if (firstRender) + { + await LoadSharedKeyAndQrCodeUriAsync(); + _isLoading = false; + StateHasChanged(); + await JsInvokeService.RetryInvokeAsync("generateQrCode", TimeSpan.Zero, 5, "authenticator-uri"); + } } private async Task OnValidSubmitAsync() @@ -85,8 +96,13 @@ else // Strip spaces and hyphens var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty); - var is2FaTokenValid = await UserManager.VerifyTwoFactorTokenAsync( - UserService.User(), UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); + var user = await UserManager.FindByIdAsync(UserService.User().Id); + if (user == null) + { + throw new InvalidOperationException("User not found."); + } + + var is2FaTokenValid = await UserManager.VerifyTwoFactorTokenAsync(user, UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); if (!is2FaTokenValid) { @@ -94,25 +110,31 @@ else return; } - await UserManager.SetTwoFactorEnabledAsync(UserService.User(), true); + await UserManager.SetTwoFactorEnabledAsync(user, true); await AuthLoggingService.LogAuthEventSuccessAsync(UserService.User().UserName!, AuthEventType.TwoFactorAuthEnable); Logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", UserService.User().Id); GlobalNotificationService.AddSuccessMessage("Your authenticator app has been verified."); - if (await UserManager.CountRecoveryCodesAsync(UserService.User()) == 0) + if (await UserManager.CountRecoveryCodesAsync(user) == 0) { - RecoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(UserService.User(), 10); + RecoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); } else { - // Navigate back to the two factor authentication page. + // Navigate back to the two-factor authentication page. NavigationService.RedirectTo("account/manage/2fa", forceLoad: true); } } - private async ValueTask LoadSharedKeyAndQrCodeUriAsync(AdminUser user) + private async ValueTask LoadSharedKeyAndQrCodeUriAsync() { - // Load the authenticator key & QR code URI to display on the form + var user = await UserManager.FindByIdAsync(UserService.User().Id); + if (user == null) + { + throw new InvalidOperationException("User not found."); + } + + // Load the authenticator key & QR code URI to display on the form. var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user); if (string.IsNullOrEmpty(unformattedKey)) { @@ -126,7 +148,7 @@ else AuthenticatorUri = GenerateQrCodeUri(username!, unformattedKey!); } - private string FormatKey(string unformattedKey) + private static string FormatKey(string unformattedKey) { var result = new StringBuilder(); int currentPosition = 0; diff --git a/src/AliasVault.Admin/Main/Pages/Account/Manage/GenerateRecoveryCodes.razor b/src/AliasVault.Admin/Main/Pages/Account/Manage/GenerateRecoveryCodes.razor index b33340c0..67e10222 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/Manage/GenerateRecoveryCodes.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/Manage/GenerateRecoveryCodes.razor @@ -7,9 +7,9 @@ Generate two-factor authentication (2FA) recovery codes -@if (recoveryCodes is not null) +@if (_recoveryCodes is not null) { - + } else { @@ -35,14 +35,20 @@ else } @code { - private IEnumerable? recoveryCodes; + private IEnumerable? _recoveryCodes; /// protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - var isTwoFactorEnabled = await UserManager.GetTwoFactorEnabledAsync(UserService.User()); + var user = await UserManager.FindByIdAsync(UserService.User().Id); + if (user == null) + { + throw new InvalidOperationException("User not found."); + } + + var isTwoFactorEnabled = await UserManager.GetTwoFactorEnabledAsync(user); if (!isTwoFactorEnabled) { throw new InvalidOperationException("Cannot generate recovery codes for user because they do not have 2FA enabled."); @@ -51,11 +57,16 @@ else private async Task GenerateCodes() { - var userId = await UserManager.GetUserIdAsync(UserService.User()); - recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(UserService.User(), 10); + var user = await UserManager.FindByIdAsync(UserService.User().Id); + if (user == null) + { + throw new InvalidOperationException("User not found."); + } + + _recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); GlobalNotificationService.AddSuccessMessage("You have generated new recovery codes."); - Logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); + Logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", UserService.User().Id); } } diff --git a/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor b/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor index 708a355a..fda5231e 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor @@ -30,10 +30,15 @@ @code { private async Task OnSubmitAsync() { - await UserManager.SetTwoFactorEnabledAsync(UserService.User(), false); - await UserManager.ResetAuthenticatorKeyAsync(UserService.User()); - var userId = await UserManager.GetUserIdAsync(UserService.User()); - Logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", userId); + var user = await UserManager.FindByIdAsync(UserService.User().Id); + if (user == null) + { + throw new InvalidOperationException("User not found."); + } + + await UserManager.SetTwoFactorEnabledAsync(user, false); + await UserManager.ResetAuthenticatorKeyAsync(user); + Logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", UserService.User().Id); GlobalNotificationService.AddSuccessMessage("Your authenticator app key has been reset, you will need to re-configure your authenticator app using the new key."); diff --git a/src/AliasVault.Admin/Main/Pages/Account/Manage/TwoFactorAuthentication.razor b/src/AliasVault.Admin/Main/Pages/Account/Manage/TwoFactorAuthentication.razor index de59bcb5..bc3cdd63 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/Manage/TwoFactorAuthentication.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/Manage/TwoFactorAuthentication.razor @@ -5,29 +5,29 @@ Two-factor authentication (2FA) -@if (is2FaEnabled) +@if (_is2FaEnabled) {

Two-factor authentication (2FA)

- @if (recoveryCodesLeft == 0) + @if (_recoveryCodesLeft == 0) {

You have no recovery codes left.

You must generate a new set of recovery codes before you can log in with a recovery code.

} - else if (recoveryCodesLeft == 1) + else if (_recoveryCodesLeft == 1) {

You have 1 recovery code left.

You can generate a new set of recovery codes.

} - else if (recoveryCodesLeft <= 3) + else if (_recoveryCodesLeft <= 3) {
-

You have @recoveryCodesLeft recovery codes left.

+

You have @_recoveryCodesLeft recovery codes left.

You should generate a new set of recovery codes.

} @@ -42,7 +42,7 @@

Authenticator app

- @if (!hasAuthenticator) + @if (!_hasAuthenticator) { } @@ -55,17 +55,23 @@
@code { - private bool hasAuthenticator; - private int recoveryCodesLeft; - private bool is2FaEnabled; + private bool _hasAuthenticator; + private int _recoveryCodesLeft; + private bool _is2FaEnabled; /// protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - hasAuthenticator = await UserManager.GetAuthenticatorKeyAsync(UserService.User()) is not null; - is2FaEnabled = await UserManager.GetTwoFactorEnabledAsync(UserService.User()); - recoveryCodesLeft = await UserManager.CountRecoveryCodesAsync(UserService.User()); + var user = await UserManager.FindByIdAsync(UserService.User().Id); + if (user == null) + { + throw new InvalidOperationException("User not found."); + } + + _hasAuthenticator = await UserManager.GetAuthenticatorKeyAsync(user) is not null; + _is2FaEnabled = await UserManager.GetTwoFactorEnabledAsync(user); + _recoveryCodesLeft = await UserManager.CountRecoveryCodesAsync(user); } } diff --git a/src/AliasVault.Admin/Main/Pages/MainBase.cs b/src/AliasVault.Admin/Main/Pages/MainBase.cs index 07cf8832..a83febf4 100644 --- a/src/AliasVault.Admin/Main/Pages/MainBase.cs +++ b/src/AliasVault.Admin/Main/Pages/MainBase.cs @@ -84,15 +84,6 @@ public abstract class MainBase : OwningComponentBase ///
protected List BreadcrumbItems { get; } = new(); - /// - /// Gets the AliasServerDbContext instance asynchronously. - /// - /// The AliasServerDbContext instance. - protected async Task GetDbContextAsync() - { - return await DbContextFactory.CreateDbContextAsync(); - } - /// protected override async Task OnInitializedAsync() { diff --git a/src/AliasVault.Admin/Main/Pages/Settings/Components/TaskRunnerHistory.razor b/src/AliasVault.Admin/Main/Pages/Settings/Components/TaskRunnerHistory.razor index a87726f7..f6a43b3e 100644 --- a/src/AliasVault.Admin/Main/Pages/Settings/Components/TaskRunnerHistory.razor +++ b/src/AliasVault.Admin/Main/Pages/Settings/Components/TaskRunnerHistory.razor @@ -64,7 +64,7 @@ ///
public async Task RefreshData() { - var dbContext = await DbContextFactory.CreateDbContextAsync(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); var query = dbContext.TaskRunnerJobs.AsQueryable(); // Apply sorting diff --git a/src/AliasVault.Admin/Main/Pages/Settings/Server.razor b/src/AliasVault.Admin/Main/Pages/Settings/Server.razor index 1c0676f6..f03bb359 100644 --- a/src/AliasVault.Admin/Main/Pages/Settings/Server.razor +++ b/src/AliasVault.Admin/Main/Pages/Settings/Server.razor @@ -122,7 +122,7 @@ { try { - var dbContext = await DbContextFactory.CreateDbContextAsync(); + await using var dbContext = await DbContextFactory.CreateDbContextAsync(); var job = new TaskRunnerJob { Name = nameof(TaskRunnerJobType.Maintenance), diff --git a/src/AliasVault.Admin/Services/UserService.cs b/src/AliasVault.Admin/Services/UserService.cs index 491f4134..da076091 100644 --- a/src/AliasVault.Admin/Services/UserService.cs +++ b/src/AliasVault.Admin/Services/UserService.cs @@ -20,7 +20,6 @@ namespace AliasVault.Admin.Services; /// HttpContextManager instance. public class UserService(IAliasServerDbContextFactory dbContextFactory, UserManager userManager, IHttpContextAccessor httpContextAccessor) { - private const string AdminRole = "Admin"; private AdminUser? _user; /// @@ -28,11 +27,6 @@ public class UserService(IAliasServerDbContextFactory dbContextFactory, UserMana /// public event Action OnChange = () => { }; - /// - /// Gets a value indicating whether the User is loaded and available, false if not. Use this before accessing User() method. - /// - public bool UserLoaded => _user != null; - /// /// Returns all users. /// @@ -85,7 +79,7 @@ public async Task LoadCurrentUserAsync() // Load user from database. Use a new context everytime to ensure we get the latest data. var userName = httpContextAccessor.HttpContext?.User.Identity?.Name ?? string.Empty; - var dbContext = await dbContextFactory.CreateDbContextAsync(); + await using var dbContext = await dbContextFactory.CreateDbContextAsync(); var user = await dbContext.AdminUsers.FirstOrDefaultAsync(u => u.UserName == userName); if (user != null) { diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs index 27f723b2..9300c14f 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs @@ -15,8 +15,10 @@ namespace AliasVault.E2ETests.Infrastructure; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Npgsql; /// /// Admin web application factory fixture for integration tests. @@ -25,15 +27,10 @@ namespace AliasVault.E2ETests.Infrastructure; public class WebApplicationAdminFactoryFixture : WebApplicationFactory where TEntryPoint : class { - /// - /// The DbConnection instance that is created for the test. - /// - private DbConnection _dbConnection; - /// /// The DbContextFactory instance that is created for the test. /// - private IDbContextFactory _dbContextFactory = null!; + private IAliasServerDbContextFactory _dbContextFactory = null!; /// /// The cached DbContext instance that can be used during the test. @@ -41,13 +38,9 @@ public class WebApplicationAdminFactoryFixture : WebApplicationFact private AliasServerDbContext? _dbContext; /// - /// Initializes a new instance of the class. + /// The name of the temporary test database. /// - public WebApplicationAdminFactoryFixture() - { - _dbConnection = new SqliteConnection("DataSource=:memory:"); - _dbConnection.Open(); - } + private string? _tempDbName; /// /// Gets or sets the port the web application kestrel host will listen on. @@ -70,14 +63,46 @@ public AliasServerDbContext GetDbContext() } /// - /// Disposes the DbConnection instance. + /// Disposes the DbConnection instance and drops the temporary database. /// - /// ValueTask. - public override ValueTask DisposeAsync() + /// Task. + public override async ValueTask DisposeAsync() { - _dbConnection.Dispose(); + if (_dbContext != null) + { + await _dbContext.DisposeAsync(); + _dbContext = null; + } + + if (!string.IsNullOrEmpty(_tempDbName)) + { + // Create a connection to 'postgres' database to drop the test database + using var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password"); + await conn.OpenAsync(); + + // First terminate existing connections + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = '{_tempDbName}'; + """; + await cmd.ExecuteNonQueryAsync(); + } + + // Then drop the database in a separate command + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + DROP DATABASE IF EXISTS "{_tempDbName}"; + """; + await cmd.ExecuteNonQueryAsync(); + } + } + GC.SuppressFinalize(this); - return base.DisposeAsync(); + await base.DisposeAsync(); } /// @@ -92,7 +117,7 @@ protected override IHost CreateHost(IHostBuilder builder) var host = base.CreateHost(builder); // Get the DbContextFactory instance and store it for later use during tests. - _dbContextFactory = host.Services.GetRequiredService>(); + _dbContextFactory = host.Services.GetRequiredService(); return host; } @@ -102,6 +127,20 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) { SetEnvironmentVariables(); + builder.ConfigureAppConfiguration((context, configBuilder) => + { + configBuilder.Sources.Clear(); + + _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; + + configBuilder.AddJsonFile("appsettings.json", optional: true); + configBuilder.AddInMemoryCollection(new Dictionary + { + ["DatabaseProvider"] = "postgresql", + ["ConnectionStrings:AliasServerDbContext"] = $"Host=localhost;Port=5432;Database={_tempDbName};Username=aliasvault;Password=password", + }); + }); + builder.ConfigureServices(services => { RemoveExistingRegistrations(services); @@ -126,7 +165,6 @@ private static void SetEnvironmentVariables() private static void RemoveExistingRegistrations(IServiceCollection services) { var descriptorsToRemove = services.Where(d => - d.ServiceType.ToString().Contains("AliasServerDbContext") || d.ServiceType == typeof(VersionedContentService)).ToList(); foreach (var descriptor in descriptorsToRemove) @@ -142,10 +180,10 @@ private static void RemoveExistingRegistrations(IServiceCollection services) private void AddNewRegistrations(IServiceCollection services) { // Add the DbContextFactory - services.AddDbContextFactory(options => + /*services.AddDbContextFactory(options => { options.UseSqlite(_dbConnection).UseLazyLoadingProxies(); - }); + });*/ // Add the VersionedContentService services.AddSingleton(new VersionedContentService("../../../../../AliasVault.Admin/wwwroot")); diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs index b7103104..33f1962b 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs @@ -15,8 +15,10 @@ namespace AliasVault.E2ETests.Infrastructure; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Npgsql; /// /// API web application factory fixture for integration tests. @@ -25,15 +27,10 @@ namespace AliasVault.E2ETests.Infrastructure; public class WebApplicationApiFactoryFixture : WebApplicationFactory where TEntryPoint : class { - /// - /// The DbConnection instance that is created for the test. - /// - private DbConnection _dbConnection; - /// /// The DbContextFactory instance that is created for the test. /// - private IDbContextFactory _dbContextFactory = null!; + private IAliasServerDbContextFactory _dbContextFactory = null!; /// /// The cached DbContext instance that can be used during the test. @@ -41,13 +38,9 @@ public class WebApplicationApiFactoryFixture : WebApplicationFactor private AliasServerDbContext? _dbContext; /// - /// Initializes a new instance of the class. + /// The name of the temporary test database. /// - public WebApplicationApiFactoryFixture() - { - _dbConnection = new SqliteConnection("DataSource=:memory:"); - _dbConnection.Open(); - } + private string? _tempDbName; /// /// Gets or sets the port the web application kestrel host will listen on. @@ -75,14 +68,46 @@ public AliasServerDbContext GetDbContext() } /// - /// Disposes the DbConnection instance. + /// Disposes the DbConnection instance and drops the temporary database. /// - /// ValueTask. - public override ValueTask DisposeAsync() + /// Task. + public override async ValueTask DisposeAsync() { - _dbConnection.Dispose(); + if (_dbContext != null) + { + await _dbContext.DisposeAsync(); + _dbContext = null; + } + + if (!string.IsNullOrEmpty(_tempDbName)) + { + // Create a connection to 'postgres' database to drop the test database + using var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password"); + await conn.OpenAsync(); + + // First terminate existing connections + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = '{_tempDbName}'; + """; + await cmd.ExecuteNonQueryAsync(); + } + + // Then drop the database in a separate command + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + DROP DATABASE IF EXISTS "{_tempDbName}"; + """; + await cmd.ExecuteNonQueryAsync(); + } + } + GC.SuppressFinalize(this); - return base.DisposeAsync(); + await base.DisposeAsync(); } /// @@ -97,7 +122,7 @@ protected override IHost CreateHost(IHostBuilder builder) var host = base.CreateHost(builder); // Get the DbContextFactory instance and store it for later use during tests. - _dbContextFactory = host.Services.GetRequiredService>(); + _dbContextFactory = host.Services.GetRequiredService(); return host; } @@ -107,6 +132,20 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) { SetEnvironmentVariables(); + builder.ConfigureAppConfiguration((context, configBuilder) => + { + configBuilder.Sources.Clear(); + + _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; + + configBuilder.AddJsonFile("appsettings.json", optional: true); + configBuilder.AddInMemoryCollection(new Dictionary + { + ["DatabaseProvider"] = "postgresql", + ["ConnectionStrings:AliasServerDbContext"] = $"Host=localhost;Port=5432;Database={_tempDbName};Username=aliasvault;Password=password", + }); + }); + builder.ConfigureServices(services => { RemoveExistingRegistrations(services); @@ -131,7 +170,6 @@ private static void SetEnvironmentVariables() private static void RemoveExistingRegistrations(IServiceCollection services) { var descriptorsToRemove = services.Where(d => - d.ServiceType.ToString().Contains("AliasServerDbContext") || d.ServiceType == typeof(ITimeProvider)).ToList(); foreach (var descriptor in descriptorsToRemove) @@ -147,10 +185,11 @@ private static void RemoveExistingRegistrations(IServiceCollection services) private void AddNewRegistrations(IServiceCollection services) { // Add the DbContextFactory - services.AddDbContextFactory(options => + /*services.AddDbContextFactory(options => { options.UseSqlite(_dbConnection).UseLazyLoadingProxies(); - }); + });*/ + // services.AddSingleton(); // Add TestTimeProvider services.AddSingleton(TimeProvider); diff --git a/src/Tests/AliasVault.E2ETests/Tests/Admin/ServerSettingsTests.cs b/src/Tests/AliasVault.E2ETests/Tests/Admin/ServerSettingsTests.cs index 1867d0ae..7ab8fa3b 100644 --- a/src/Tests/AliasVault.E2ETests/Tests/Admin/ServerSettingsTests.cs +++ b/src/Tests/AliasVault.E2ETests/Tests/Admin/ServerSettingsTests.cs @@ -38,8 +38,8 @@ public async Task ServerSettingsMutationTest() await Page.Locator("input[id='schedule']").FillAsync("03:30"); // Uncheck Sunday and Saturday from maintenance days - await Page.Locator("input[id='day_7']").UncheckAsync(); // Sunday await Page.Locator("input[id='day_6']").UncheckAsync(); // Saturday + await Page.Locator("input[id='day_7']").UncheckAsync(); // Sunday // Save changes var saveButton = Page.Locator("text=Save changes"); @@ -75,6 +75,9 @@ public async Task ServerSettingsMutationTest() await Page.ReloadAsync(); await WaitForUrlAsync("settings/server", "Server settings"); + // Wait for 0.5sec to ensure the page is fully loaded. + await Task.Delay(500); + var generalLogRetentionValue = await Page.Locator("input[id='generalLogRetention']").InputValueAsync(); Assert.That(generalLogRetentionValue, Is.EqualTo("45"), "General log retention value not persisted after refresh"); diff --git a/src/Tests/AliasVault.E2ETests/Tests/Admin/TwoFactorAuthLockoutTests.cs b/src/Tests/AliasVault.E2ETests/Tests/Admin/TwoFactorAuthLockoutTests.cs index a8d488e0..5c8a6af1 100644 --- a/src/Tests/AliasVault.E2ETests/Tests/Admin/TwoFactorAuthLockoutTests.cs +++ b/src/Tests/AliasVault.E2ETests/Tests/Admin/TwoFactorAuthLockoutTests.cs @@ -29,6 +29,9 @@ public async Task AccountLockoutTest2Fa() var enable2FaButton = Page.GetByRole(AriaRole.Link, new() { Name = "Add authenticator app" }); await enable2FaButton.ClickAsync(); + // Wait for QR code to appear. + await WaitForUrlAsync("account/manage/enable-authenticator", "Scan the QR Code or enter this key"); + // Extract secret key from page. var secretKey = await Page.TextContentAsync("kbd"); From 78a872a67d5ef58acbd172ba684bcb52d81edcb5 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 12:57:24 +0100 Subject: [PATCH 13/62] Refactor smtpserver and taskrunner so all tests pass (#190) --- .../Workers/TaskRunnerWorker.cs | 6 +- .../Services/ServerSettingsService.cs | 6 +- .../Client/Shard1/EmailDecryptionTests.cs | 1 + .../SmtpServer/SmtpServerTests.cs | 1 + .../SmtpServer/TestHostBuilder.cs | 116 +++++++++++++---- .../TaskRunner/TaskRunnerTests.cs | 56 ++++++-- .../TaskRunner/TestHostBuilder.cs | 121 +++++++++++++----- 7 files changed, 232 insertions(+), 75 deletions(-) diff --git a/src/Services/AliasVault.TaskRunner/Workers/TaskRunnerWorker.cs b/src/Services/AliasVault.TaskRunner/Workers/TaskRunnerWorker.cs index 75fde18e..9284bbf9 100644 --- a/src/Services/AliasVault.TaskRunner/Workers/TaskRunnerWorker.cs +++ b/src/Services/AliasVault.TaskRunner/Workers/TaskRunnerWorker.cs @@ -35,7 +35,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await using var dbContext = await dbContextFactory.CreateDbContextAsync(stoppingToken); var settings = await settingsService.GetAllSettingsAsync(); - var now = DateTime.Now; + var now = DateTime.UtcNow; var today = now.Date; // Check for on-demand run request @@ -95,7 +95,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) /// The cancellation token. private async Task ExecuteMaintenanceTasks(TaskRunnerJob job, AliasServerDbContext dbContext, CancellationToken stoppingToken) { - logger.LogWarning("Starting maintenance tasks at {Time} (On-demand: {IsOnDemand})", DateTime.Now, job.IsOnDemand); + logger.LogWarning("Starting maintenance tasks at {Time} (On-demand: {IsOnDemand})", DateTime.UtcNow, job.IsOnDemand); try { @@ -124,7 +124,7 @@ private async Task ExecuteMaintenanceTasks(TaskRunnerJob job, AliasServerDbConte } finally { - job.EndTime = TimeOnly.FromDateTime(DateTime.Now); + job.EndTime = TimeOnly.FromDateTime(DateTime.UtcNow); await dbContext.SaveChangesAsync(stoppingToken); } diff --git a/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs b/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs index c0030514..5b509767 100644 --- a/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs +++ b/src/Shared/AliasVault.Shared.Server/Services/ServerSettingsService.cs @@ -36,7 +36,7 @@ public class ServerSettingsService(IAliasServerDbContextFactory dbContextFactory return cachedValue; } - await using var dbContext = dbContextFactory.CreateDbContext(); + await using var dbContext = await dbContextFactory.CreateDbContextAsync(); var setting = await dbContext.ServerSettings.FirstOrDefaultAsync(x => x.Key == key); _cache[key] = setting?.Value; @@ -57,7 +57,7 @@ public async Task SetSettingAsync(string key, string? value) return; } - await using var dbContext = dbContextFactory.CreateDbContext(); + await using var dbContext = await dbContextFactory.CreateDbContextAsync(); var setting = await dbContext.ServerSettings.FirstOrDefaultAsync(x => x.Key == key); var now = DateTime.UtcNow; @@ -96,7 +96,7 @@ public async Task SetSettingAsync(string key, string? value) /// The settings. public async Task GetAllSettingsAsync() { - await using var dbContext = dbContextFactory.CreateDbContext(); + await using var dbContext = await dbContextFactory.CreateDbContextAsync(); var settings = await dbContext.ServerSettings.ToDictionaryAsync(x => x.Key, x => x.Value); // Create model with defaults diff --git a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/EmailDecryptionTests.cs b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/EmailDecryptionTests.cs index b3bd432d..4145ab0a 100644 --- a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/EmailDecryptionTests.cs +++ b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard1/EmailDecryptionTests.cs @@ -212,6 +212,7 @@ public async Task TearDown() { await _testHost.StopAsync(); _testHost.Dispose(); + await _testHostBuilder.DisposeAsync(); } /// diff --git a/src/Tests/AliasVault.IntegrationTests/SmtpServer/SmtpServerTests.cs b/src/Tests/AliasVault.IntegrationTests/SmtpServer/SmtpServerTests.cs index 53ad2f62..54f5fc48 100644 --- a/src/Tests/AliasVault.IntegrationTests/SmtpServer/SmtpServerTests.cs +++ b/src/Tests/AliasVault.IntegrationTests/SmtpServer/SmtpServerTests.cs @@ -105,6 +105,7 @@ public async Task TearDown() { await _testHost.StopAsync(); _testHost.Dispose(); + await _testHostBuilder.DisposeAsync(); } /// diff --git a/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs b/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs index 54f56181..0525c0f5 100644 --- a/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs +++ b/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs @@ -9,46 +9,50 @@ namespace AliasVault.IntegrationTests.SmtpServer; using System.Data.Common; using AliasServerDb; +using AliasServerDb.Configuration; using AliasVault.SmtpService; using AliasVault.SmtpService.Handlers; using AliasVault.SmtpService.Workers; using global::SmtpServer; using global::SmtpServer.Storage; -using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Npgsql; /// /// Builder class for creating a test host for the SmtpServiceWorker in order to run integration tests against it. /// -public class TestHostBuilder +public class TestHostBuilder : IAsyncDisposable { /// - /// The DbConnection instance that is created for the test. + /// The DbContextFactory instance that is created for the test. /// - private DbConnection? _dbConnection; + private IAliasServerDbContextFactory _dbContextFactory = null!; /// - /// The DbContext instance that is created for the test. + /// The cached DbContext instance that can be used during the test. /// private AliasServerDbContext? _dbContext; + /// + /// The temporary database name for the test. + /// + private string? _tempDbName; + /// /// Returns the DbContext instance for the test. This can be used to seed the database with test data. /// /// AliasServerDbContext instance. public AliasServerDbContext GetDbContext() { - if (_dbContext == null) + if (_dbContext != null) { - var options = new DbContextOptionsBuilder() - .UseSqlite(_dbConnection!) - .Options; - - _dbContext = new AliasServerDbContext(options); + return _dbContext; } + _dbContext = _dbContextFactory.CreateDbContext(); return _dbContext; } @@ -58,40 +62,55 @@ public AliasServerDbContext GetDbContext() /// IHost. public IHost Build() { - // Create a persistent in-memory database for the duration of the test. - var dbConnection = new SqliteConnection("DataSource=:memory:"); - dbConnection.Open(); + _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; + + // Create a connection to 'postgres' database to ensure the test database exists + using (var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password")) + { + conn.Open(); + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + CREATE DATABASE "{_tempDbName}"; + """; + cmd.ExecuteNonQuery(); + } + } + + // Create a connection to the new test database + var dbConnection = new NpgsqlConnection($"Host=localhost;Port=5432;Database={_tempDbName};Username=aliasvault;Password=password"); return Build(dbConnection); } - /// + /// /// Builds the SmtpService test host with a provided database connection. /// /// The database connection to use for the test. /// IHost. public IHost Build(DbConnection dbConnection) { - // Create a persistent in-memory database for the duration of the test. - _dbConnection = dbConnection; - var builder = Host.CreateDefaultBuilder() .ConfigureServices((context, services) => { + // Override configuration + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: true) + .AddInMemoryCollection(new Dictionary + { + ["DatabaseProvider"] = "postgresql", + ["ConnectionStrings:AliasServerDbContext"] = dbConnection.ConnectionString, + }) + .Build(); + + services.AddSingleton(configuration); + services.AddSingleton(new Config { AllowedToDomains = new List { "example.tld" }, SmtpTlsEnabled = "false", }); - services.AddSingleton(_dbConnection); - - services.AddDbContextFactory((sp, options) => - { - var connection = sp.GetRequiredService(); - options.UseSqlite(connection); - }); - services.AddTransient(); services.AddSingleton( provider => @@ -112,18 +131,59 @@ public IHost Build(DbConnection dbConnection) return new SmtpServer(options.Build(), provider.GetRequiredService()); }); + services.AddAliasVaultDatabaseConfiguration(configuration); services.AddHostedService(); // Ensure the in-memory database is populated with tables var serviceProvider = services.BuildServiceProvider(); using (var scope = serviceProvider.CreateScope()) { - var dbContextFactory = scope.ServiceProvider.GetRequiredService>(); - var dbContext = dbContextFactory.CreateDbContext(); + _dbContextFactory = scope.ServiceProvider.GetRequiredService(); + var dbContext = _dbContextFactory.CreateDbContext(); dbContext.Database.Migrate(); } }); return builder.Build(); } + + /// + /// Disposes of the test host and cleans up the temporary database. + /// + /// A representing the asynchronous operation. + public async ValueTask DisposeAsync() + { + if (_dbContext != null) + { + await _dbContext.DisposeAsync(); + _dbContext = null; + } + + if (!string.IsNullOrEmpty(_tempDbName)) + { + // Create a connection to 'postgres' database to drop the test database + using var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password"); + await conn.OpenAsync(); + + // First terminate existing connections + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = '{_tempDbName}'; + """; + await cmd.ExecuteNonQueryAsync(); + } + + // Then drop the database + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + DROP DATABASE IF EXISTS "{_tempDbName}"; + """; + await cmd.ExecuteNonQueryAsync(); + } + } + } } diff --git a/src/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs b/src/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs index baec7b37..d703dd76 100644 --- a/src/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs +++ b/src/Tests/AliasVault.IntegrationTests/TaskRunner/TaskRunnerTests.cs @@ -7,6 +7,7 @@ namespace AliasVault.IntegrationTests.TaskRunner; +using AliasVault.Shared.Models.Enums; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; @@ -45,6 +46,7 @@ public async Task TearDown() { await _testHost.StopAsync(); _testHost.Dispose(); + await _testHostBuilder.DisposeAsync(); } /// @@ -58,7 +60,7 @@ public async Task EmailCleanup() await InitializeWithTestData(); // Assert - var dbContext = _testHostBuilder.GetDbContext(); + await using var dbContext = await _testHostBuilder.GetDbContextAsync(); var emails = await dbContext.Emails.ToListAsync(); Assert.That(emails, Has.Count.EqualTo(50)); } @@ -74,7 +76,7 @@ public async Task LogCleanup() await InitializeWithTestData(); // Assert - var dbContext = _testHostBuilder.GetDbContext(); + await using var dbContext = await _testHostBuilder.GetDbContextAsync(); var generalLogs = await dbContext.Logs.ToListAsync(); Assert.That(generalLogs, Has.Count.EqualTo(50), "Only recent general logs should remain"); } @@ -90,7 +92,7 @@ public async Task AuthLogCleanup() await InitializeWithTestData(); // Assert - var dbContext = _testHostBuilder.GetDbContext(); + await using var dbContext = await _testHostBuilder.GetDbContextAsync(); // Check auth logs var authLogs = await dbContext.AuthLogs.ToListAsync(); @@ -105,7 +107,8 @@ public async Task AuthLogCleanup() public async Task MaintenanceTimeInFutureDoesNotRun() { // Seed database with generic test data. - await SeedData.SeedDatabase(_testHostBuilder.GetDbContext()); + await using var dbContext = await _testHostBuilder.GetDbContextAsync(); + await SeedData.SeedDatabase(dbContext); // Update maintenance time in database to future to ensure the task runner doesn't execute yet. @@ -120,7 +123,6 @@ public async Task MaintenanceTimeInFutureDoesNotRun() } // Update maintenance time in database - var dbContext = _testHostBuilder.GetDbContext(); var maintenanceTimeSetting = await dbContext.ServerSettings .FirstAsync(s => s.Key == "MaintenanceTime"); maintenanceTimeSetting.Value = futureTime.ToString("HH:mm"); @@ -145,14 +147,13 @@ public async Task MaintenanceTimeInFutureDoesNotRun() public async Task MaintenanceTimeExcludedDayDoesNotRun() { // Seed database with generic test data. - await SeedData.SeedDatabase(_testHostBuilder.GetDbContext()); + await using var dbContext = await _testHostBuilder.GetDbContextAsync(); + await SeedData.SeedDatabase(dbContext); // Get current day of week (1-7, Monday = 1, Sunday = 7) var currentDay = (int)DateTime.Now.DayOfWeek + 1; // Update maintenance settings in database to exclude current day - var dbContext = _testHostBuilder.GetDbContext(); - // Set maintenance time to midnight var maintenanceTimeSetting = await dbContext.ServerSettings .FirstAsync(s => s.Key == "MaintenanceTime"); @@ -185,7 +186,44 @@ public async Task MaintenanceTimeExcludedDayDoesNotRun() /// Task. protected async Task InitializeWithTestData() { - await SeedData.SeedDatabase(_testHostBuilder.GetDbContext()); + await using var dbContext = await _testHostBuilder.GetDbContextAsync(); + await SeedData.SeedDatabase(dbContext); await _testHost.StartAsync(); + + // Wait for the maintenance job to complete instead of using a fixed delay + await WaitForMaintenanceJobCompletion(); + } + + /// + /// Waits for the maintenance job to complete. + /// + /// The timeout in seconds. + /// Task. + protected async Task WaitForMaintenanceJobCompletion(int timeoutSeconds = 10) + { + var startTime = DateTime.Now; + var timeout = startTime.AddSeconds(timeoutSeconds); + + while (DateTime.Now < timeout) + { + await using var dbContext = await _testHostBuilder.GetDbContextAsync(); + var job = await dbContext.TaskRunnerJobs + .OrderByDescending(j => j.Id) + .FirstOrDefaultAsync(); + + if (job != null && (job.Status == TaskRunnerJobStatus.Finished || job.Status == TaskRunnerJobStatus.Error)) + { + if (job.Status == TaskRunnerJobStatus.Error) + { + Assert.Fail($"Maintenance job failed with error: {job.ErrorMessage}"); + } + + return; + } + + await Task.Delay(500); // Poll every 500ms + } + + Assert.Fail($"Maintenance job did not complete within {timeoutSeconds} seconds"); } } diff --git a/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs b/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs index 7bcad8de..835c48d7 100644 --- a/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs +++ b/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs @@ -7,45 +7,44 @@ namespace AliasVault.IntegrationTests.TaskRunner; -using System.Data.Common; using AliasServerDb; +using AliasServerDb.Configuration; using AliasVault.Shared.Server.Services; -using AliasVault.TaskRunner; using AliasVault.TaskRunner.Tasks; using AliasVault.TaskRunner.Workers; -using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Npgsql; /// /// Builder class for creating a test host for the TaskRunner in order to run integration tests against it. /// -public class TestHostBuilder +public class TestHostBuilder : IAsyncDisposable { /// - /// The DbConnection instance that is created for the test. + /// The DbContextFactory instance that is created for the test. /// - private DbConnection? _dbConnection; + private IAliasServerDbContextFactory _dbContextFactory = null!; + /// + /// The cached DbContext instance that can be used during the test. + /// private AliasServerDbContext? _dbContext; /// - /// Returns the DbContext instance for the test. + /// The temporary database name for the test. + /// + private string? _tempDbName; + + /// + /// Returns the DbContext instance for the test. This can be used to seed the database with test data. /// /// AliasServerDbContext instance. - public AliasServerDbContext GetDbContext() + public async Task GetDbContextAsync() { - if (_dbContext == null) - { - var options = new DbContextOptionsBuilder() - .UseSqlite(_dbConnection!) - .Options; - - _dbContext = new AliasServerDbContext(options); - } - - return _dbContext; + return await _dbContextFactory.CreateDbContextAsync(); } /// @@ -54,21 +53,37 @@ public AliasServerDbContext GetDbContext() /// IHost. public IHost Build() { - // Create a persistent in-memory database for the duration of the test - var dbConnection = new SqliteConnection("DataSource=:memory:"); - dbConnection.Open(); - _dbConnection = dbConnection; + // Create a temporary database for the test + _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; + // Create a connection to 'postgres' database to create the test database + using (var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password")) + { + conn.Open(); + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + CREATE DATABASE "{_tempDbName}"; + """; + cmd.ExecuteNonQuery(); + } + } + + // Create the connection to the new test database + var dbConnection = new NpgsqlConnection($"Host=localhost;Port=5432;Database={_tempDbName};Username=aliasvault;Password=password"); var builder = Host.CreateDefaultBuilder() .ConfigureServices((context, services) => { - services.AddSingleton(_dbConnection); - - services.AddDbContextFactory((sp, options) => - { - var connection = sp.GetRequiredService(); - options.UseSqlite(connection); - }); + // Override configuration + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: true) + .AddInMemoryCollection(new Dictionary + { + ["DatabaseProvider"] = "postgresql", + ["ConnectionStrings:AliasServerDbContext"] = dbConnection.ConnectionString, + }) + .Build(); + services.AddSingleton(configuration); // Add server settings service services.AddSingleton(); @@ -78,19 +93,61 @@ public IHost Build() services.AddTransient(); services.AddTransient(); + services.AddAliasVaultDatabaseConfiguration(configuration); + // Add the TaskRunner worker services.AddHostedService(); - // Ensure the in-memory database is populated with tables + // Ensure the database is populated with tables var serviceProvider = services.BuildServiceProvider(); using (var scope = serviceProvider.CreateScope()) { - var dbContextFactory = scope.ServiceProvider.GetRequiredService>(); - var dbContext = dbContextFactory.CreateDbContext(); + _dbContextFactory = scope.ServiceProvider.GetRequiredService(); + var dbContext = _dbContextFactory.CreateDbContext(); dbContext.Database.Migrate(); } }); return builder.Build(); } + + /// + /// Disposes of the test host and cleans up the temporary database. + /// + /// A representing the asynchronous operation. + public async ValueTask DisposeAsync() + { + if (_dbContext != null) + { + await _dbContext.DisposeAsync(); + _dbContext = null; + } + + if (!string.IsNullOrEmpty(_tempDbName)) + { + // Create a connection to 'postgres' database to drop the test database + using var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password"); + await conn.OpenAsync(); + + // First terminate existing connections + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = '{_tempDbName}'; + """; + await cmd.ExecuteNonQueryAsync(); + } + + // Then drop the database + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + DROP DATABASE IF EXISTS "{_tempDbName}"; + """; + await cmd.ExecuteNonQueryAsync(); + } + } + } } From 9735df0436d5ea71fc5874b4d28bc373f1e91100 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 13:57:01 +0100 Subject: [PATCH 14/62] Update install.sh to generate postgresql credentials (#190) --- .gitignore | 4 ++ README.md | 2 +- docker-compose.build.yml | 6 --- docker-compose.yml | 6 +-- docs/installation/install.md | 2 +- docs/misc/dev/postgresql-commands.md | 51 +++++++++++++++++++ install.sh | 13 +++++ .../Configuration/DatabaseConfiguration.cs | 25 +++++++-- 8 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 docs/misc/dev/postgresql-commands.md diff --git a/.gitignore b/.gitignore index 06a5bfb4..994133da 100644 --- a/.gitignore +++ b/.gitignore @@ -272,6 +272,10 @@ ServiceFabricBackup/ *.sqlite-shm *.sqlite-wal +# SQL files +*.sql +*.sql.gz + # Business Intelligence projects *.rdl.data *.bim.layout diff --git a/README.md b/README.md index 2cab01fb..6da5cf30 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ This method uses pre-built Docker images and works on minimal hardware specifica - Linux VM with root access (Ubuntu or RHEL based distros recommended) - 1 vCPU -- 512MB RAM +- 1GB RAM - 16GB disk space - Docker installed diff --git a/docker-compose.build.yml b/docker-compose.build.yml index dc6447a1..6478998c 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -42,9 +42,3 @@ services: dockerfile: Dockerfile.postgres ports: - "5432:5432" - volumes: - - ./database/postgres:/var/lib/postgresql/data:rw - environment: - POSTGRES_DB: aliasvault - POSTGRES_USER: aliasvault - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} diff --git a/docker-compose.yml b/docker-compose.yml index 7947271b..012ba083 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -92,8 +92,6 @@ services: image: ghcr.io/lanedirt/aliasvault-postgres:latest volumes: - ./database/postgres:/var/lib/postgresql/data:rw - environment: - POSTGRES_DB: aliasvault - POSTGRES_USER: aliasvault - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + env_file: + - .env restart: always diff --git a/docs/installation/install.md b/docs/installation/install.md index 5a874638..e1857b46 100644 --- a/docs/installation/install.md +++ b/docs/installation/install.md @@ -20,7 +20,7 @@ To get AliasVault up and running quickly, run the install script to pull pre-bui ### Hardware requirements - Linux VM with root access (Ubuntu or RHEL based distros recommended) - 1 vCPU -- 512MB RAM +- 1GB RAM - 16GB disk space - Docker installed diff --git a/docs/misc/dev/postgresql-commands.md b/docs/misc/dev/postgresql-commands.md new file mode 100644 index 00000000..375948f8 --- /dev/null +++ b/docs/misc/dev/postgresql-commands.md @@ -0,0 +1,51 @@ +--- +layout: default +title: PostgreSQL Commands +parent: Development +grand_parent: Miscellaneous +nav_order: 1 +--- + +# PostgreSQL Commands + +## Backup database to file +To backup the database to a file, you can use the following command: + +```bash +docker compose exec postgres pg_dump -U aliasvault aliasvault | gzip > aliasvault.sql.gz +``` + +## Import database from file +To drop the existing database and restore the database from a file, you can use the following command: + +{: .warning } +Executing this command will drop the existing database and restore the database from the file. Make sure to have a backup of the existing database before running this command. + +```bash +docker compose exec postgres psql -U aliasvault postgres -c "DROP DATABASE aliasvault;" && \ +docker compose exec postgres psql -U aliasvault postgres -c "CREATE DATABASE aliasvault;" && \ +gunzip < aliasvault.sql.gz | docker compose exec -iT postgres psql -U aliasvault aliasvault +``` + +## Change master password +By default during initial installation the PostgreSQL master password is set to a random string that is +stored in the `.env` file with the `POSTGRES_PASSWORD` variable. + +If you wish to change the master password, you can do so by running the following command: + +1. Open a terminal and navigate to the root of the AliasVault repository. +2. Run the following command to connect to the PostgreSQL container: + ```bash + docker compose exec -it postgres psql -U aliasvault -d aliasvault + ``` +3. Once connected to the database, you can change the master password by running the following command: + ```sql + ALTER USER aliasvault WITH PASSWORD 'new_password'; + ``` +4. Press Enter to confirm the changes. +5. Exit the PostgreSQL shell by running `\q`. +6. Manually update the `.env` file variable `POSTGRES_PASSWORD` with the new password. +7. Restart the AliasVault containers by running the following command: + ```bash + docker compose restart + ``` diff --git a/install.sh b/install.sh index f46f50b0..6a02b528 100755 --- a/install.sh +++ b/install.sh @@ -338,6 +338,17 @@ populate_data_protection_cert_pass() { fi } +populate_postgres_password() { + printf "${CYAN}> Checking POSTGRES_PASSWORD...${NC}\n" + if ! grep -q "^POSTGRES_PASSWORD=" "$ENV_FILE" || [ -z "$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d '=' -f2)" ]; then + # Generate a strong random password with 32 characters + POSTGRES_PASS=$(openssl rand -base64 32) + update_env_var "POSTGRES_PASSWORD" "$POSTGRES_PASS" + else + printf " ${GREEN}> POSTGRES_PASSWORD already exists.${NC}\n" + fi +} + set_private_email_domains() { printf "${CYAN}> Checking PRIVATE_EMAIL_DOMAINS...${NC}\n" if ! grep -q "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" || [ -z "$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)" ]; then @@ -683,6 +694,7 @@ handle_build() { populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; } populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; } populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; } + populate_postgres_password || { printf "${RED}> Failed to set PostgreSQL password${NC}\n"; exit 1; } set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; } set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; } set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; } @@ -1365,6 +1377,7 @@ handle_install_version() { populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; } populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; } populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; } + populate_postgres_password || { printf "${RED}> Failed to set PostgreSQL password${NC}\n"; exit 1; } set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; } set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; } set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; } diff --git a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs index e68b75a9..841cf254 100644 --- a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs +++ b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs @@ -23,9 +23,28 @@ public static class DatabaseConfiguration /// The IServiceCollection for method chaining. public static IServiceCollection AddAliasVaultDatabaseConfiguration(this IServiceCollection services, IConfiguration configuration) { - var dbProvider = configuration.GetValue("DatabaseProvider")?.ToLower() ?? "sqlite"; + // Check for environment variable first, then fall back to configuration + var connectionString = Environment.GetEnvironmentVariable("ConnectionStrings__AliasServerDbContext"); + var dbProvider = Environment.GetEnvironmentVariable("DatabaseProvider")?.ToLower() + ?? configuration.GetValue("DatabaseProvider")?.ToLower() + ?? "postgresql"; - // Add custom DbContextFactory registration which supports multiple database providers. + // Create a new configuration if we have an environment-provided connection string + if (!string.IsNullOrEmpty(connectionString)) + { + var configDictionary = new Dictionary + { + ["ConnectionStrings:AliasServerDbContext"] = connectionString, + }; + + var configurationBuilder = new ConfigurationBuilder() + .AddConfiguration(configuration) + .AddInMemoryCollection(configDictionary); + + configuration = configurationBuilder.Build(); + } + + // Add custom DbContextFactory registration which supports multiple database providers switch (dbProvider) { case "postgresql": @@ -41,7 +60,7 @@ public static IServiceCollection AddAliasVaultDatabaseConfiguration(this IServic services.AddDbContextFactory((sp, options) => { var factory = sp.GetRequiredService(); - factory.ConfigureDbContextOptions(options); // Let the factory configure the options directly + factory.ConfigureDbContextOptions(options); }); // Add scoped DbContext registration based on the factory From 219f0bc9cc0d3d29207f344013bfa72ef4c5b6d7 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 14:17:17 +0100 Subject: [PATCH 15/62] Update db configuration (#190) --- .env.example | 2 ++ install.sh | 6 +++--- .../Configuration/DatabaseConfiguration.cs | 10 ++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index a18d6992..d7058c39 100644 --- a/.env.example +++ b/.env.example @@ -6,3 +6,5 @@ ADMIN_PASSWORD_GENERATED=2024-01-01T00:00:00Z PRIVATE_EMAIL_DOMAINS= SMTP_TLS_ENABLED=false LETSENCRYPT_ENABLED=false +POSTGRES_PASSWORD= +SUPPORT_EMAIL= diff --git a/install.sh b/install.sh index 6a02b528..442bb017 100755 --- a/install.sh +++ b/install.sh @@ -357,7 +357,7 @@ set_private_email_domains() { private_email_domains=$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2) if [ "$private_email_domains" = "DISABLED.TLD" ]; then - printf " ${RED}Email server is disabled.${NC} To enable use ./install.sh configure-email command.\n" + printf " Email server is disabled. To enable use ./install.sh configure-email command.\n" else printf " ${GREEN}> PRIVATE_EMAIL_DOMAINS already exists. Email server is enabled.${NC}\n" fi @@ -692,12 +692,12 @@ handle_build() { # Initialize environment with proper error handling create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; } populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; } + set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; } populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; } populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; } populate_postgres_password || { printf "${RED}> Failed to set PostgreSQL password${NC}\n"; exit 1; } set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; } set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; } - set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; } set_default_ports || { printf "${RED}> Failed to set default ports${NC}\n"; exit 1; } set_public_registration || { printf "${RED}> Failed to set public registration${NC}\n"; exit 1; } @@ -1375,12 +1375,12 @@ handle_install_version() { # Initialize environment create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; } populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; } + set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; } populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; } populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; } populate_postgres_password || { printf "${RED}> Failed to set PostgreSQL password${NC}\n"; exit 1; } set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; } set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; } - set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; } set_default_ports || { printf "${RED}> Failed to set default ports${NC}\n"; exit 1; } set_public_registration || { printf "${RED}> Failed to set public registration${NC}\n"; exit 1; } diff --git a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs index 841cf254..14262fc8 100644 --- a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs +++ b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs @@ -23,25 +23,27 @@ public static class DatabaseConfiguration /// The IServiceCollection for method chaining. public static IServiceCollection AddAliasVaultDatabaseConfiguration(this IServiceCollection services, IConfiguration configuration) { - // Check for environment variable first, then fall back to configuration + // Check for environment variables first, then fall back to configuration var connectionString = Environment.GetEnvironmentVariable("ConnectionStrings__AliasServerDbContext"); var dbProvider = Environment.GetEnvironmentVariable("DatabaseProvider")?.ToLower() ?? configuration.GetValue("DatabaseProvider")?.ToLower() ?? "postgresql"; - // Create a new configuration if we have an environment-provided connection string + // Create a new configuration if we have environment-provided values if (!string.IsNullOrEmpty(connectionString)) { var configDictionary = new Dictionary { ["ConnectionStrings:AliasServerDbContext"] = connectionString, + ["DatabaseProvider"] = dbProvider, }; var configurationBuilder = new ConfigurationBuilder() - .AddConfiguration(configuration) .AddInMemoryCollection(configDictionary); - configuration = configurationBuilder.Build(); + // Only add the original configuration after our environment variables + // This ensures environment variables take precedence + configurationBuilder.AddConfiguration(configuration).Build(); } // Add custom DbContextFactory registration which supports multiple database providers From 728b20b489a9a5c479ced69603115d1f9ede3e8d Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 14:18:47 +0100 Subject: [PATCH 16/62] Update install.sh (#190) --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 442bb017..fa80bec0 100755 --- a/install.sh +++ b/install.sh @@ -357,7 +357,7 @@ set_private_email_domains() { private_email_domains=$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2) if [ "$private_email_domains" = "DISABLED.TLD" ]; then - printf " Email server is disabled. To enable use ./install.sh configure-email command.\n" + printf " ${GREEN}> Email server is disabled. To enable use ./install.sh configure-email command.${NC}\n" else printf " ${GREEN}> PRIVATE_EMAIL_DOMAINS already exists. Email server is enabled.${NC}\n" fi From d9f4f8d121985956b7e8c030b02897963a853ffd Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 14:33:36 +0100 Subject: [PATCH 17/62] Update Dockerfile (#190) --- src/AliasVault.Client/Dockerfile | 42 ++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/AliasVault.Client/Dockerfile b/src/AliasVault.Client/Dockerfile index 3a795238..55b0ff17 100644 --- a/src/AliasVault.Client/Dockerfile +++ b/src/AliasVault.Client/Dockerfile @@ -1,33 +1,49 @@ FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build +ARG TARGETARCH ARG BUILD_CONFIGURATION=Release ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 ENV MSBUILDDEBUGPATH=/src/msbuild-logs -WORKDIR /src +WORKDIR /source -# Create the debug directory and install Python which is required by the WebAssembly tools -RUN mkdir -p /src/msbuild-logs && apt-get update && apt-get install -y python3 && apt-get clean +# Install Python which is required by the WebAssembly tools +RUN apt-get update && apt-get install -y python3 && apt-get clean # Install the WebAssembly tools RUN dotnet workload install wasm-tools -# Copy the project files and restore dependencies +# Copy all project files first COPY ["src/AliasVault.Client/AliasVault.Client.csproj", "src/AliasVault.Client/"] -RUN dotnet restore "src/AliasVault.Client/AliasVault.Client.csproj" +COPY ["src/Databases/AliasClientDb/AliasClientDb.csproj", "src/Databases/AliasClientDb/"] +COPY ["src/Utilities/Cryptography/AliasVault.Cryptography.Client/AliasVault.Cryptography.Client.csproj", "src/Utilities/Cryptography/AliasVault.Cryptography.Client/"] +COPY ["src/Utilities/AliasVault.FaviconExtractor/AliasVault.FaviconExtractor.csproj", "src/Utilities/AliasVault.FaviconExtractor/"] +COPY ["src/Generators/AliasVault.Generators.Identity/AliasVault.Generators.Identity.csproj", "src/Generators/AliasVault.Generators.Identity/"] +COPY ["src/Utilities/AliasVault.CsvImportExport/AliasVault.CsvImportExport.csproj", "src/Utilities/AliasVault.CsvImportExport/"] +COPY ["src/Shared/AliasVault.Shared/AliasVault.Shared.csproj", "src/Shared/AliasVault.Shared/"] +COPY ["src/Shared/AliasVault.Shared.Core/AliasVault.Shared.Core.csproj", "src/Shared/AliasVault.Shared.Core/"] +COPY ["src/Generators/AliasVault.Generators.Password/AliasVault.Generators.Password.csproj", "src/Generators/AliasVault.Generators.Password/"] +COPY ["src/Shared/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj", "src/Shared/AliasVault.RazorComponents/"] + +# Restore all packages +RUN dotnet restore "src/AliasVault.Client/AliasVault.Client.csproj" -a $TARGETARCH + +# Copy everything else and build COPY . . - -# Build and publish -WORKDIR "/src/src/AliasVault.Client" -RUN dotnet publish "AliasVault.Client.csproj" -c "$BUILD_CONFIGURATION" -o /app/publish /p:UseAppHost=false +RUN dotnet publish "src/AliasVault.Client/AliasVault.Client.csproj" \ + -a $TARGETARCH \ + -c "$BUILD_CONFIGURATION" \ + --no-restore \ + -o /app/publish \ + /p:UseAppHost=false # Final stage -FROM nginx:1.24.0 AS final +FROM nginx:1.24.0-alpine WORKDIR /usr/share/nginx/html COPY --from=build /app/publish/wwwroot . -COPY /src/AliasVault.Client/nginx.conf /etc/nginx/nginx.conf -COPY /src/AliasVault.Client/entrypoint.sh /app/entrypoint.sh +COPY src/AliasVault.Client/nginx.conf /etc/nginx/nginx.conf +COPY src/AliasVault.Client/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh From 564ae54de89eca113f0bab919c04b34abffbca12 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 15:11:34 +0100 Subject: [PATCH 18/62] Update postgresql factory to support env vars if available (#190) --- .../PostgresqlDbContextFactory.cs | 9 +++- .../WebApplicationAdminFactoryFixture.cs | 46 +++++------------- .../WebApplicationApiFactoryFixture.cs | 47 +++++-------------- 3 files changed, 32 insertions(+), 70 deletions(-) diff --git a/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs index 0d5bb6e1..6170e0ce 100644 --- a/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs +++ b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs @@ -44,7 +44,14 @@ public Task CreateDbContextAsync(CancellationToken cancell /// public void ConfigureDbContextOptions(DbContextOptionsBuilder optionsBuilder) { - var connectionString = _configuration.GetConnectionString("AliasServerDbContext"); + // Check environment variable first. + var connectionString = Environment.GetEnvironmentVariable("ConnectionStrings__AliasServerDbContext"); + + // If no environment variable, fall back to configuration. + if (string.IsNullOrEmpty(connectionString)) + { + connectionString = _configuration.GetConnectionString("AliasServerDbContext"); + } optionsBuilder .UseNpgsql(connectionString, options => options.CommandTimeout(60)) diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs index 9300c14f..3ea30a8a 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs @@ -7,15 +7,11 @@ namespace AliasVault.E2ETests.Infrastructure; -using System.Data.Common; using AliasServerDb; using AliasVault.Admin.Services; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Npgsql; @@ -125,22 +121,9 @@ protected override IHost CreateHost(IHostBuilder builder) /// protected override void ConfigureWebHost(IWebHostBuilder builder) { + _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; SetEnvironmentVariables(); - builder.ConfigureAppConfiguration((context, configBuilder) => - { - configBuilder.Sources.Clear(); - - _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; - - configBuilder.AddJsonFile("appsettings.json", optional: true); - configBuilder.AddInMemoryCollection(new Dictionary - { - ["DatabaseProvider"] = "postgresql", - ["ConnectionStrings:AliasServerDbContext"] = $"Host=localhost;Port=5432;Database={_tempDbName};Username=aliasvault;Password=password", - }); - }); - builder.ConfigureServices(services => { RemoveExistingRegistrations(services); @@ -148,16 +131,6 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) }); } - /// - /// Sets the required environment variables for testing. - /// - private static void SetEnvironmentVariables() - { - Environment.SetEnvironmentVariable("ADMIN_PASSWORD_HASH", "AQAAAAIAAYagAAAAEKWfKfa2gh9Z72vjAlnNP1xlME7FsunRznzyrfqFte40FToufRwa3kX8wwDwnEXZag=="); - Environment.SetEnvironmentVariable("ADMIN_PASSWORD_GENERATED", "2024-01-01T00:00:00Z"); - Environment.SetEnvironmentVariable("DATA_PROTECTION_CERT_PASS", "Development"); - } - /// /// Removes existing service registrations. /// @@ -173,18 +146,23 @@ private static void RemoveExistingRegistrations(IServiceCollection services) } } + /// + /// Sets the required environment variables for testing. + /// + private void SetEnvironmentVariables() + { + Environment.SetEnvironmentVariable("ConnectionStrings__AliasServerDbContext", "Host=postgres;Database=" + _tempDbName + ";Username=aliasvault;Password=password"); + Environment.SetEnvironmentVariable("ADMIN_PASSWORD_HASH", "AQAAAAIAAYagAAAAEKWfKfa2gh9Z72vjAlnNP1xlME7FsunRznzyrfqFte40FToufRwa3kX8wwDwnEXZag=="); + Environment.SetEnvironmentVariable("ADMIN_PASSWORD_GENERATED", "2024-01-01T00:00:00Z"); + Environment.SetEnvironmentVariable("DATA_PROTECTION_CERT_PASS", "Development"); + } + /// /// Adds new service registrations. /// /// The to modify. private void AddNewRegistrations(IServiceCollection services) { - // Add the DbContextFactory - /*services.AddDbContextFactory(options => - { - options.UseSqlite(_dbConnection).UseLazyLoadingProxies(); - });*/ - // Add the VersionedContentService services.AddSingleton(new VersionedContentService("../../../../../AliasVault.Admin/wwwroot")); diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs index 33f1962b..c39fc87c 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs @@ -7,15 +7,11 @@ namespace AliasVault.E2ETests.Infrastructure; -using System.Data.Common; using AliasServerDb; using AliasVault.Shared.Providers.Time; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Npgsql; @@ -130,22 +126,9 @@ protected override IHost CreateHost(IHostBuilder builder) /// protected override void ConfigureWebHost(IWebHostBuilder builder) { + _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; SetEnvironmentVariables(); - builder.ConfigureAppConfiguration((context, configBuilder) => - { - configBuilder.Sources.Clear(); - - _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; - - configBuilder.AddJsonFile("appsettings.json", optional: true); - configBuilder.AddInMemoryCollection(new Dictionary - { - ["DatabaseProvider"] = "postgresql", - ["ConnectionStrings:AliasServerDbContext"] = $"Host=localhost;Port=5432;Database={_tempDbName};Username=aliasvault;Password=password", - }); - }); - builder.ConfigureServices(services => { RemoveExistingRegistrations(services); @@ -153,16 +136,6 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) }); } - /// - /// Sets the required environment variables for testing. - /// - private static void SetEnvironmentVariables() - { - Environment.SetEnvironmentVariable("JWT_KEY", "12345678901234567890123456789012"); - Environment.SetEnvironmentVariable("DATA_PROTECTION_CERT_PASS", "Development"); - Environment.SetEnvironmentVariable("PUBLIC_REGISTRATION_ENABLED", "true"); - } - /// /// Removes existing service registrations. /// @@ -178,19 +151,23 @@ private static void RemoveExistingRegistrations(IServiceCollection services) } } + /// + /// Sets the required environment variables for testing. + /// + private void SetEnvironmentVariables() + { + Environment.SetEnvironmentVariable("ConnectionStrings__AliasServerDbContext", "Host=postgres;Database=" + _tempDbName + ";Username=aliasvault;Password=password"); + Environment.SetEnvironmentVariable("JWT_KEY", "12345678901234567890123456789012"); + Environment.SetEnvironmentVariable("DATA_PROTECTION_CERT_PASS", "Development"); + Environment.SetEnvironmentVariable("PUBLIC_REGISTRATION_ENABLED", "true"); + } + /// /// Adds new service registrations. /// /// The to modify. private void AddNewRegistrations(IServiceCollection services) { - // Add the DbContextFactory - /*services.AddDbContextFactory(options => - { - options.UseSqlite(_dbConnection).UseLazyLoadingProxies(); - });*/ - // services.AddSingleton(); - // Add TestTimeProvider services.AddSingleton(TimeProvider); } From 32c8e48d45e129d224afc6a6cf1a48d1c4343e58 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 15:25:18 +0100 Subject: [PATCH 19/62] Update Dockerfile (#190) --- src/AliasVault.Client/Dockerfile | 50 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/AliasVault.Client/Dockerfile b/src/AliasVault.Client/Dockerfile index 55b0ff17..2ea28ab8 100644 --- a/src/AliasVault.Client/Dockerfile +++ b/src/AliasVault.Client/Dockerfile @@ -1,49 +1,49 @@ FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base WORKDIR /app -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build -ARG TARGETARCH +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 ENV MSBUILDDEBUGPATH=/src/msbuild-logs -WORKDIR /source +WORKDIR /src + +# Create the debug directory +RUN mkdir -p /src/msbuild-logs # Install Python which is required by the WebAssembly tools RUN apt-get update && apt-get install -y python3 && apt-get clean +# Create the debug directory and install Python which is required by the WebAssembly tools +RUN mkdir -p /src/msbuild-logs && apt-get update && apt-get install -y python3 && apt-get clean # Install the WebAssembly tools RUN dotnet workload install wasm-tools -# Copy all project files first +# Copy the project files and restore dependencies COPY ["src/AliasVault.Client/AliasVault.Client.csproj", "src/AliasVault.Client/"] -COPY ["src/Databases/AliasClientDb/AliasClientDb.csproj", "src/Databases/AliasClientDb/"] -COPY ["src/Utilities/Cryptography/AliasVault.Cryptography.Client/AliasVault.Cryptography.Client.csproj", "src/Utilities/Cryptography/AliasVault.Cryptography.Client/"] -COPY ["src/Utilities/AliasVault.FaviconExtractor/AliasVault.FaviconExtractor.csproj", "src/Utilities/AliasVault.FaviconExtractor/"] -COPY ["src/Generators/AliasVault.Generators.Identity/AliasVault.Generators.Identity.csproj", "src/Generators/AliasVault.Generators.Identity/"] -COPY ["src/Utilities/AliasVault.CsvImportExport/AliasVault.CsvImportExport.csproj", "src/Utilities/AliasVault.CsvImportExport/"] -COPY ["src/Shared/AliasVault.Shared/AliasVault.Shared.csproj", "src/Shared/AliasVault.Shared/"] -COPY ["src/Shared/AliasVault.Shared.Core/AliasVault.Shared.Core.csproj", "src/Shared/AliasVault.Shared.Core/"] -COPY ["src/Generators/AliasVault.Generators.Password/AliasVault.Generators.Password.csproj", "src/Generators/AliasVault.Generators.Password/"] -COPY ["src/Shared/AliasVault.RazorComponents/AliasVault.RazorComponents.csproj", "src/Shared/AliasVault.RazorComponents/"] - -# Restore all packages -RUN dotnet restore "src/AliasVault.Client/AliasVault.Client.csproj" -a $TARGETARCH - -# Copy everything else and build +RUN dotnet restore "src/AliasVault.Client/AliasVault.Client.csproj" COPY . . -RUN dotnet publish "src/AliasVault.Client/AliasVault.Client.csproj" \ - -a $TARGETARCH \ + +# Build the Client project +WORKDIR "/src/src/AliasVault.Client" +RUN dotnet build "AliasVault.Client.csproj" -c "$BUILD_CONFIGURATION" -o /app/build + +# Publish the Client project +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "AliasVault.Client.csproj" \ -c "$BUILD_CONFIGURATION" \ --no-restore \ -o /app/publish \ - /p:UseAppHost=false + /p:UseAppHost=false \ + /p:WasmNativeStrip=false \ + /p:EmccInitialHeapSize=268435456 # Final stage -FROM nginx:1.24.0-alpine +FROM nginx:1.24.0 AS final WORKDIR /usr/share/nginx/html -COPY --from=build /app/publish/wwwroot . -COPY src/AliasVault.Client/nginx.conf /etc/nginx/nginx.conf -COPY src/AliasVault.Client/entrypoint.sh /app/entrypoint.sh +COPY --from=publish /app/publish/wwwroot . +COPY /src/AliasVault.Client/nginx.conf /etc/nginx/nginx.conf +COPY /src/AliasVault.Client/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh From 0a577873ee914d480b1a892b9f6e61a0670f1a72 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 15:40:00 +0100 Subject: [PATCH 20/62] Update install.sh to create postgres credentials (#190) --- install.sh | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index fa80bec0..8614a1bd 100755 --- a/install.sh +++ b/install.sh @@ -338,8 +338,21 @@ populate_data_protection_cert_pass() { fi } -populate_postgres_password() { - printf "${CYAN}> Checking POSTGRES_PASSWORD...${NC}\n" +populate_postgres_credentials() { + printf "${CYAN}> Checking Postgres credentials...${NC}\n" + + if ! grep -q "^POSTGRES_DB=" "$ENV_FILE" || [ -z "$(grep "^POSTGRES_DB=" "$ENV_FILE" | cut -d '=' -f2)" ]; then + update_env_var "POSTGRES_DB" "aliasvault" + else + printf " ${GREEN}> POSTGRES_DB already exists.${NC}\n" + fi + + if ! grep -q "^POSTGRES_USER=" "$ENV_FILE" || [ -z "$(grep "^POSTGRES_USER=" "$ENV_FILE" | cut -d '=' -f2)" ]; then + update_env_var "POSTGRES_USER" "aliasvault" + else + printf " ${GREEN}> POSTGRES_USER already exists.${NC}\n" + fi + if ! grep -q "^POSTGRES_PASSWORD=" "$ENV_FILE" || [ -z "$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d '=' -f2)" ]; then # Generate a strong random password with 32 characters POSTGRES_PASS=$(openssl rand -base64 32) @@ -695,7 +708,7 @@ handle_build() { set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; } populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; } populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; } - populate_postgres_password || { printf "${RED}> Failed to set PostgreSQL password${NC}\n"; exit 1; } + populate_postgres_credentials || { printf "${RED}> Failed to set PostgreSQL credentials${NC}\n"; exit 1; } set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; } set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; } set_default_ports || { printf "${RED}> Failed to set default ports${NC}\n"; exit 1; } @@ -1378,7 +1391,7 @@ handle_install_version() { set_support_email || { printf "${RED}> Failed to set support email${NC}\n"; exit 1; } populate_jwt_key || { printf "${RED}> Failed to set JWT key${NC}\n"; exit 1; } populate_data_protection_cert_pass || { printf "${RED}> Failed to set certificate password${NC}\n"; exit 1; } - populate_postgres_password || { printf "${RED}> Failed to set PostgreSQL password${NC}\n"; exit 1; } + populate_postgres_credentials || { printf "${RED}> Failed to set PostgreSQL credentials${NC}\n"; exit 1; } set_private_email_domains || { printf "${RED}> Failed to set email domains${NC}\n"; exit 1; } set_smtp_tls_enabled || { printf "${RED}> Failed to set SMTP TLS${NC}\n"; exit 1; } set_default_ports || { printf "${RED}> Failed to set default ports${NC}\n"; exit 1; } From cf454d2bb8bd34df7a3c8b4ba6bc37a568474c37 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 16:03:59 +0100 Subject: [PATCH 21/62] Add postgres healthcheck to docker-compose.yml (#190) --- docker-compose.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 012ba083..220e5cb0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,7 +39,8 @@ services: env_file: - .env depends_on: - - postgres + postgres: + condition: service_healthy environment: ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" @@ -55,7 +56,8 @@ services: env_file: - .env depends_on: - - postgres + postgres: + condition: service_healthy environment: ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" @@ -71,7 +73,8 @@ services: env_file: - .env depends_on: - - postgres + postgres: + condition: service_healthy environment: ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" @@ -84,7 +87,8 @@ services: env_file: - .env depends_on: - - postgres + postgres: + condition: service_healthy environment: ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" @@ -95,3 +99,9 @@ services: env_file: - .env restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U aliasvault"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s From 4fb5087c82e2f5a448fc41a9bf01828954a9d444 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 16:31:02 +0100 Subject: [PATCH 22/62] Add local postgresql dev database (#190) --- .gitignore | 1 + CONTRIBUTING.md | 29 +++-- docker-compose.build.yml | 2 - docker-compose.dev.yml | 20 +++ docs/misc/dev/contributing.md | 114 ++++++++++++++++++ docs/misc/dev/enable-webauthn-pfr-chrome.md | 2 +- docs/misc/dev/postgresql-commands.md | 2 +- docs/misc/dev/run-github-actions-locally.md | 2 +- install.sh | 99 +++++++++++++++ src/AliasVault.Admin/appsettings.json | 2 +- src/AliasVault.Api/appsettings.json | 2 +- .../AliasVault.SmtpService/appsettings.json | 2 +- .../AliasVault.TaskRunner/appsettings.json | 2 +- .../WebApplicationAdminFactoryFixture.cs | 2 +- .../WebApplicationApiFactoryFixture.cs | 2 +- .../SmtpServer/TestHostBuilder.cs | 6 +- .../TaskRunner/TestHostBuilder.cs | 6 +- 17 files changed, 270 insertions(+), 25 deletions(-) create mode 100644 docker-compose.dev.yml create mode 100644 docs/misc/dev/contributing.md diff --git a/.gitignore b/.gitignore index 994133da..46bbb308 100644 --- a/.gitignore +++ b/.gitignore @@ -414,3 +414,4 @@ docs/.bundle # Database files database/postgres +database/postgres-dev diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e2dfc848..dc96c710 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,16 +26,16 @@ cp .github/hooks/commit-msg .git/hooks/commit-msg chmod +x .git/hooks/commit-msg ``` -### 3. Install the latest version of .NET SDK 8 +### 3. Install the latest version of .NET SDK 9 ```bash -# Install .NET SDK 8 +# Install .NET SDK 9 # On MacOS via brew: brew install --cask dotnet-sdk # On Windows via winget -winget install Microsoft.DotNet.SDK.8 +winget install Microsoft.DotNet.SDK.9 ``` ### 4. Install dotnet CLI EF Tools @@ -51,13 +51,26 @@ export PATH="$PATH:$HOME/.dotnet/tools" dotnet ef ``` -### 5. Run Tailwind CSS compiler while changing HTML files to update compiled CSS +### 5. Install dev database +AliasVault uses PostgreSQL as its database. In order to run the project locally from Visual Studio / Rider you will need to install the dev database. You can do this +by running the following command. This will start a separate PostgreSQL instance on port 5433 accessible via the `localhost:5433` address. ```bash -npm run build:css +./install.sh configure-dev-db ``` -### 6. Install Playwright in order to locally run NUnit E2E (end-to-end) tests +This will start the dev database and create a new database for the project. + +### 6. Run Tailwind CSS compiler when changing HTML files to update compiled CSS + +```bash +# For Admin project (in the admin project directory) +npm run build:admin-css +# For Client project (in the client project directory) +npm run build:client-css +``` + +### 7. Install Playwright in order to locally run NUnit E2E (end-to-end) tests ```bash # First install PowerShell for Mac (if you don't have it already) @@ -66,10 +79,10 @@ brew install powershell/tap/powershell dotnet tool install --global Microsoft.Playwright.CLI # Run Playwright install script to download local browsers # Note: make sure the E2E test project has been built at least once so the bin dir exists. -pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net8.0/playwright.ps1 install +pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install ``` -### 7. Create AliasVault.Client appsettings.Development.json +### 8. Create AliasVault.Client appsettings.Development.json The WASM client app supports a development specific appsettings.json file. This appsettings file is optional but can override various options to make debugging easier. diff --git a/docker-compose.build.yml b/docker-compose.build.yml index 6478998c..cd1382bf 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -40,5 +40,3 @@ services: build: context: . dockerfile: Dockerfile.postgres - ports: - - "5432:5432" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 00000000..29dc7586 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,20 @@ +services: + postgres-dev: + image: postgres:16-alpine + ports: + - "5433:5432" + volumes: + - ./database/postgres-dev:/var/lib/postgresql/data:rw + - ./postgresql.conf:/etc/postgresql/postgresql.conf + environment: + - POSTGRES_DB=aliasvault + - POSTGRES_USER=aliasvault + - POSTGRES_PASSWORD=password + restart: "no" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U aliasvault"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s + command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] \ No newline at end of file diff --git a/docs/misc/dev/contributing.md b/docs/misc/dev/contributing.md new file mode 100644 index 00000000..19a84157 --- /dev/null +++ b/docs/misc/dev/contributing.md @@ -0,0 +1,114 @@ +--- +layout: default +title: Contributing +parent: Development +grand_parent: Miscellaneous +nav_order: 1 +--- + +# Contributing +This document is a work-in-progress and will be expanded as time goes on. If you have any questions feel free to open a issue on GitHub. + +Note: all instructions below are based on MacOS. If you are using a different operating system, you may need to adjust the commands accordingly. + +## Getting Started +In order to contribute to this project follow these instructions to setup your local environment: + +### 1. Clone the repository +```bash +git clone https://github.com/lanedirt/AliasVault.git +cd AliasVault +``` + +### 2. Copy pre-commit hook script to .git/hooks directory +{: .note } +All commits in this repo are required to contain a reference to a GitHub issue in the format of "your commit message (#123)" where "123" references the GitHub issue number. + +The pre-commit hook script below will check the commit message before allowing the commit to proceed. If the commit message is invalid, the commit will be aborted. + +```bash +# Copy the commit-msg hook script to the .git/hooks directory +cp .github/hooks/commit-msg .git/hooks/commit-msg + +# Make the script executable +chmod +x .git/hooks/commit-msg +``` + +### 3. Install the latest version of .NET SDK 9 +```bash +# Install .NET SDK 9 + +# On MacOS via brew: +brew install --cask dotnet-sdk + +# On Windows via winget +winget install Microsoft.DotNet.SDK.9 +``` + +### 4. Install dotnet CLI EF Tools +```bash +# Install dotnet EF tools globally +dotnet tool install --global dotnet-ef +# Include dotnet tools in your PATH +nano ~/.zshrc +# Add the following line to your .zshrc file +export PATH="$PATH:$HOME/.dotnet/tools" +# Start a new terminal and test that this command works: +dotnet ef +``` + +### 5. Install dev database +AliasVault uses PostgreSQL as its database. In order to run the project locally from Visual Studio / Rider you will need to install the dev database. You can do this by running the following command. This will start a separate PostgreSQL instance on port 5433 accessible via the `localhost:5433` address. + +```bash +./install.sh configure-dev-db +``` + +After the database is running you can start the project from Visual Studio / Rider in run or debug mode and it should be able to connect to the dev database. + +### 6. Run Tailwind CSS compiler when changing HTML files to update compiled CSS +```bash +# For Admin project (in the admin project directory) +npm run build:admin-css +# For Client project (in the client project directory) +npm run build:client-css +``` + +### 7. Install Playwright in order to locally run NUnit E2E (end-to-end) tests +```bash +# First install PowerShell for Mac (if you don't have it already) +brew install powershell/tap/powershell +# Install Playwright +dotnet tool install --global Microsoft.Playwright.CLI +# Run Playwright install script to download local browsers +# Note: make sure the E2E test project has been built at least once so the bin dir exists. +pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install +``` + +### 8. Create AliasVault.Client appsettings.Development.json +The WASM client app supports a development specific appsettings.json file. This appsettings file is optional but can override various options to make debugging easier. + +1. Copy `wwwroot/appsettings.json` to `wwwroot/appsettings.Development.json` + +Here is an example file with the various options explained: + +```json +{ + "ApiUrl": "http://localhost:5092", + "PrivateEmailDomains": ["example.tld"], + "SupportEmail": "support@example.tld", + "UseDebugEncryptionKey": "true", + "CryptographyOverrideType" : "Argon2Id", + "CryptographyOverrideSettings" : "{\"DegreeOfParallelism\":1,\"MemorySize\":1024,\"Iterations\":1}" +} +``` + +- **UseDebugEncryptionKey** + - This setting will use a static encryption key so that if you login as a user you can refresh the page without needing to unlock the database again. This speeds up development when changing things in the WebApp WASM project. Note: the project needs to be run in "Development" mode for this setting to be used. + +- **CryptographyOverrideType** + - This setting allows overriding the default encryption type (Argon2id) with a different encryption type. This is useful for testing different encryption types without having to change code. + +- **CryptographyOverrideSettings** + - This setting allows overriding the default encryption settings (Argon2id) with different settings. This is useful for testing different encryption settings without having to change code. The default Argon2id settings are defined in the project as `Utilities/Cryptography/Cryptography.Client/Defaults.cs`. These default settings are focused on security but NOT performance. Normally for key derivation purposes the slower/heavier the algorithm the better protection against attackers. For production builds this is what we want, however in case of automated testing or debugging extra performance can be gained by tweaking (lowering) these settings. +``` \ No newline at end of file diff --git a/docs/misc/dev/enable-webauthn-pfr-chrome.md b/docs/misc/dev/enable-webauthn-pfr-chrome.md index 0be778f8..a25d8693 100644 --- a/docs/misc/dev/enable-webauthn-pfr-chrome.md +++ b/docs/misc/dev/enable-webauthn-pfr-chrome.md @@ -3,7 +3,7 @@ layout: default title: Enable WebAuthn parent: Development grand_parent: Miscellaneous -nav_order: 1 +nav_order: 9 --- # WebAuthn diff --git a/docs/misc/dev/postgresql-commands.md b/docs/misc/dev/postgresql-commands.md index 375948f8..3796a271 100644 --- a/docs/misc/dev/postgresql-commands.md +++ b/docs/misc/dev/postgresql-commands.md @@ -3,7 +3,7 @@ layout: default title: PostgreSQL Commands parent: Development grand_parent: Miscellaneous -nav_order: 1 +nav_order: 2 --- # PostgreSQL Commands diff --git a/docs/misc/dev/run-github-actions-locally.md b/docs/misc/dev/run-github-actions-locally.md index 3994192a..c519486b 100644 --- a/docs/misc/dev/run-github-actions-locally.md +++ b/docs/misc/dev/run-github-actions-locally.md @@ -3,7 +3,7 @@ layout: default title: 1. Run GitHub Actions Locally parent: Development grand_parent: Miscellaneous -nav_order: 1 +nav_order: 9 --- # Run GitHub Actions Locally diff --git a/install.sh b/install.sh index 8614a1bd..d2cfe095 100755 --- a/install.sh +++ b/install.sh @@ -50,6 +50,7 @@ show_usage() { printf " restart Restart AliasVault containers\n" printf " reset-password Reset admin password\n" printf " build Build AliasVault from source (takes longer and requires sufficient specs)\n" + printf " configure-dev-db Configure development database (for local development only)\n" printf "\n" printf "Options:\n" @@ -126,6 +127,10 @@ parse_args() { COMMAND="update-installer" shift ;; + configure-dev-db|dev-db) + COMMAND="configure-dev-db" + shift + ;; --help) show_usage exit 0 @@ -210,6 +215,9 @@ main() { check_install_script_update exit $? ;; + "configure-dev-db") + configure_dev_database + ;; esac } @@ -1438,4 +1446,95 @@ handle_install_version() { print_success_message } +# Function to handle development database configuration +configure_dev_database() { + printf "${YELLOW}+++ Development Database Configuration +++${NC}\n" + printf "\n" + + if [ ! -f "docker-compose.dev.yml" ]; then + printf "${CYAN}> Downloading docker-compose.dev.yml...${NC}\n" + curl -sSf "${GITHUB_RAW_URL_REPO_BRANCH}/docker-compose.dev.yml" -o "docker-compose.dev.yml" + fi + + # Check current status + if docker compose -f docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then + DEV_DB_STATUS="running" + else + DEV_DB_STATUS="stopped" + fi + + printf "${CYAN}About Development Database:${NC}\n" + printf "A separate PostgreSQL instance for development purposes that:\n" + printf " - Runs on port 5433 (to avoid conflicts)\n" + printf " - Uses simple credentials (password: 'password')\n" + printf " - Stores data separately from production\n" + printf "\n" + printf "${CYAN}Current Status:${NC}\n" + if [ "$DEV_DB_STATUS" = "running" ]; then + printf "Development Database: ${GREEN}Running${NC}\n" + else + printf "Development Database: ${YELLOW}Stopped${NC}\n" + fi + printf "\n" + printf "Options:\n" + printf "1) Start development database\n" + printf "2) Stop development database\n" + printf "3) View connection details\n" + printf "4) Cancel\n" + printf "\n" + + read -p "Select an option [1-4]: " dev_db_option + + case $dev_db_option in + 1) + if [ "$DEV_DB_STATUS" = "running" ]; then + printf "${YELLOW}> Development database is already running.${NC}\n" + else + printf "${CYAN}> Starting development database...${NC}\n" + docker compose -p aliasvault-dev -f docker-compose.dev.yml up -d + printf "${GREEN}> Development database started successfully.${NC}\n" + fi + print_dev_db_details + ;; + 2) + if [ "$DEV_DB_STATUS" = "stopped" ]; then + printf "${YELLOW}> Development database is already stopped.${NC}\n" + else + printf "${CYAN}> Stopping development database...${NC}\n" + docker compose -p aliasvault-dev -f docker-compose.dev.yml down + printf "${GREEN}> Development database stopped successfully.${NC}\n" + fi + ;; + 3) + print_dev_db_details + ;; + 4) + printf "${YELLOW}Configuration cancelled.${NC}\n" + exit 0 + ;; + *) + printf "${RED}Invalid option selected.${NC}\n" + exit 1 + ;; + esac +} + +# Function to print development database connection details +print_dev_db_details() { + printf "\n" + printf "${MAGENTA}=========================================================${NC}\n" + printf "\n" + printf "${CYAN}Development Database Connection Details:${NC}\n" + printf "Host: localhost\n" + printf "Port: 5433\n" + printf "Database: aliasvault\n" + printf "Username: aliasvault\n" + printf "Password: password\n" + printf "\n" + printf "Connection string:\n" + printf "Host=localhost;Port=5433;Database=aliasvault;Username=aliasvault;Password=password\n" + printf "\n" + printf "${MAGENTA}=========================================================${NC}\n" +} + main "$@" diff --git a/src/AliasVault.Admin/appsettings.json b/src/AliasVault.Admin/appsettings.json index 189a56ce..e91f3221 100644 --- a/src/AliasVault.Admin/appsettings.json +++ b/src/AliasVault.Admin/appsettings.json @@ -1,7 +1,7 @@ { "DatabaseProvider": "postgresql", "ConnectionStrings": { - "AliasServerDbContext": "Host=localhost;Port=5432;Database=aliasvault;Username=aliasvault;Password=password" + "AliasServerDbContext": "Host=localhost;Port=5433;Database=aliasvault;Username=aliasvault;Password=password" }, "Logging": { "LogLevel": { diff --git a/src/AliasVault.Api/appsettings.json b/src/AliasVault.Api/appsettings.json index 0a484e5f..0b0e6bf1 100644 --- a/src/AliasVault.Api/appsettings.json +++ b/src/AliasVault.Api/appsettings.json @@ -13,7 +13,7 @@ }, "DatabaseProvider": "postgresql", "ConnectionStrings": { - "AliasServerDbContext": "Host=localhost;Port=5432;Database=aliasvault;Username=aliasvault;Password=password" + "AliasServerDbContext": "Host=localhost;Port=5433;Database=aliasvault;Username=aliasvault;Password=password" }, "AllowedHosts": "*" } diff --git a/src/Services/AliasVault.SmtpService/appsettings.json b/src/Services/AliasVault.SmtpService/appsettings.json index e8623fc1..7feaf202 100644 --- a/src/Services/AliasVault.SmtpService/appsettings.json +++ b/src/Services/AliasVault.SmtpService/appsettings.json @@ -8,6 +8,6 @@ }, "DatabaseProvider": "postgresql", "ConnectionStrings": { - "AliasServerDbContext": "Host=localhost;Port=5432;Database=aliasvault;Username=aliasvault;Password=password" + "AliasServerDbContext": "Host=localhost;Port=5433;Database=aliasvault;Username=aliasvault;Password=password" } } diff --git a/src/Services/AliasVault.TaskRunner/appsettings.json b/src/Services/AliasVault.TaskRunner/appsettings.json index e8623fc1..7feaf202 100644 --- a/src/Services/AliasVault.TaskRunner/appsettings.json +++ b/src/Services/AliasVault.TaskRunner/appsettings.json @@ -8,6 +8,6 @@ }, "DatabaseProvider": "postgresql", "ConnectionStrings": { - "AliasServerDbContext": "Host=localhost;Port=5432;Database=aliasvault;Username=aliasvault;Password=password" + "AliasServerDbContext": "Host=localhost;Port=5433;Database=aliasvault;Username=aliasvault;Password=password" } } diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs index 3ea30a8a..370ecb25 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs @@ -73,7 +73,7 @@ public override async ValueTask DisposeAsync() if (!string.IsNullOrEmpty(_tempDbName)) { // Create a connection to 'postgres' database to drop the test database - using var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password"); + using var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password"); await conn.OpenAsync(); // First terminate existing connections diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs index c39fc87c..b48a6697 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs @@ -78,7 +78,7 @@ public override async ValueTask DisposeAsync() if (!string.IsNullOrEmpty(_tempDbName)) { // Create a connection to 'postgres' database to drop the test database - using var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password"); + using var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password"); await conn.OpenAsync(); // First terminate existing connections diff --git a/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs b/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs index 0525c0f5..1a282f58 100644 --- a/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs +++ b/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs @@ -65,7 +65,7 @@ public IHost Build() _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; // Create a connection to 'postgres' database to ensure the test database exists - using (var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password")) + using (var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password")) { conn.Open(); using (var cmd = conn.CreateCommand()) @@ -78,7 +78,7 @@ public IHost Build() } // Create a connection to the new test database - var dbConnection = new NpgsqlConnection($"Host=localhost;Port=5432;Database={_tempDbName};Username=aliasvault;Password=password"); + var dbConnection = new NpgsqlConnection($"Host=localhost;Port=5433;Database={_tempDbName};Username=aliasvault;Password=password"); return Build(dbConnection); } @@ -162,7 +162,7 @@ public async ValueTask DisposeAsync() if (!string.IsNullOrEmpty(_tempDbName)) { // Create a connection to 'postgres' database to drop the test database - using var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password"); + using var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password"); await conn.OpenAsync(); // First terminate existing connections diff --git a/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs b/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs index 835c48d7..a54325b6 100644 --- a/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs +++ b/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs @@ -57,7 +57,7 @@ public IHost Build() _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; // Create a connection to 'postgres' database to create the test database - using (var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password")) + using (var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password")) { conn.Open(); using (var cmd = conn.CreateCommand()) @@ -70,7 +70,7 @@ public IHost Build() } // Create the connection to the new test database - var dbConnection = new NpgsqlConnection($"Host=localhost;Port=5432;Database={_tempDbName};Username=aliasvault;Password=password"); + var dbConnection = new NpgsqlConnection($"Host=localhost;Port=5433;Database={_tempDbName};Username=aliasvault;Password=password"); var builder = Host.CreateDefaultBuilder() .ConfigureServices((context, services) => { @@ -126,7 +126,7 @@ public async ValueTask DisposeAsync() if (!string.IsNullOrEmpty(_tempDbName)) { // Create a connection to 'postgres' database to drop the test database - using var conn = new NpgsqlConnection("Host=localhost;Port=5432;Database=postgres;Username=aliasvault;Password=password"); + using var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password"); await conn.OpenAsync(); // First terminate existing connections From 8edfc3d0d6a191b0fac236191afdafc4c3fd3cb9 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 17:11:30 +0100 Subject: [PATCH 23/62] Update Logout.razor (#190) --- src/AliasVault.Admin/Auth/Pages/Logout.razor | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/AliasVault.Admin/Auth/Pages/Logout.razor b/src/AliasVault.Admin/Auth/Pages/Logout.razor index abaa3306..7ecf11b9 100644 --- a/src/AliasVault.Admin/Auth/Pages/Logout.razor +++ b/src/AliasVault.Admin/Auth/Pages/Logout.razor @@ -22,14 +22,13 @@ await AuthLoggingService.LogAuthEventSuccessAsync(username!, AuthEventType.Logout); // Redirect to the home page with hard refresh. - NavigationService.RedirectTo("/", true); + NavigationService.RedirectTo("./", true); } - catch (Exception ex) + catch { // Hard refresh current page if sign out fails. When an interactive server session is already started // the sign-out will fail because it tries to mutate cookies which is only possible when the server // session is not started yet. - Console.WriteLine(ex); await AuthLoggingService.LogAuthEventSuccessAsync(username!, AuthEventType.Logout); NavigationService.RedirectTo(NavigationService.Uri, true); } From 30804cc973eb5cdf6454c6ee9d15f213f3b76b31 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 17:18:03 +0100 Subject: [PATCH 24/62] Update DataProtectionExtensions.cs (#190) --- src/AliasVault.Admin/Program.cs | 2 +- .../DataProtectionExtensions.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/AliasVault.Admin/Program.cs b/src/AliasVault.Admin/Program.cs index 1e272a76..ae2abb8c 100644 --- a/src/AliasVault.Admin/Program.cs +++ b/src/AliasVault.Admin/Program.cs @@ -38,7 +38,7 @@ builder.Services.AddSingleton(config); -builder.Services.AddAliasVaultDataProtection("AliasVault.Api"); +builder.Services.AddAliasVaultDataProtection("AliasVault.Admin"); // Add services to the container. builder.Services.AddRazorComponents() diff --git a/src/Utilities/Cryptography/AliasVault.Cryptography.Server/DataProtectionExtensions.cs b/src/Utilities/Cryptography/AliasVault.Cryptography.Server/DataProtectionExtensions.cs index 9ea29e43..eeb615fe 100644 --- a/src/Utilities/Cryptography/AliasVault.Cryptography.Server/DataProtectionExtensions.cs +++ b/src/Utilities/Cryptography/AliasVault.Cryptography.Server/DataProtectionExtensions.cs @@ -36,6 +36,10 @@ public static IServiceCollection AddAliasVaultDataProtection( certPath = Path.Combine(AppContext.BaseDirectory, $"{applicationName}.DataProtection.Development.pfx"); } + var certificateFlags = X509KeyStorageFlags.MachineKeySet | + X509KeyStorageFlags.PersistKeySet | + X509KeyStorageFlags.Exportable; + X509Certificate2 cert; if (!File.Exists(certPath)) { @@ -44,7 +48,7 @@ public static IServiceCollection AddAliasVaultDataProtection( } else { - cert = X509CertificateLoader.LoadPkcs12FromFile(certPath, certPassword, X509KeyStorageFlags.DefaultKeySet); + cert = X509CertificateLoader.LoadPkcs12FromFile(certPath, certPassword, certificateFlags); } services.AddDataProtection() From 4b8e4c907e53b47c400aa60439d9f95e6216fbb9 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 20:10:47 +0100 Subject: [PATCH 25/62] Refactor WebApplicationFactoryFixture (#190) --- .../Abstracts/WebApplicationFactoryFixture.cs | 163 ++++++++++++++++++ .../WebApplicationAdminFactoryFixture.cs | 129 +------------- .../WebApplicationApiFactoryFixture.cs | 134 +------------- 3 files changed, 173 insertions(+), 253 deletions(-) create mode 100644 src/Tests/AliasVault.E2ETests/Infrastructure/Abstracts/WebApplicationFactoryFixture.cs diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/Abstracts/WebApplicationFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/Abstracts/WebApplicationFactoryFixture.cs new file mode 100644 index 00000000..bc14b20e --- /dev/null +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/Abstracts/WebApplicationFactoryFixture.cs @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) lanedirt. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +//----------------------------------------------------------------------- + +namespace AliasVault.E2ETests.Infrastructure.Abstracts; + +using AliasServerDb; +using AliasVault.Shared.Providers.Time; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Npgsql; + +/// +/// Base class for web application factory fixture for integration tests. +/// +/// The entry point. +public abstract class WebApplicationFactoryFixture : WebApplicationFactory + where TEntryPoint : class +{ + /// + /// The DbContextFactory instance that is created for the test. + /// + private IAliasServerDbContextFactory _dbContextFactory = null!; + + /// + /// The cached DbContext instance that can be used during the test. + /// + private AliasServerDbContext? _dbContext; + + /// + /// The name of the temporary test database. + /// + private string? _tempDbName; + + /// + /// Gets or sets the port the web application kestrel host will listen on. + /// + public abstract int Port { get; set; } + + /// + /// Gets the time provider instance for mutating the current time in tests. + /// + public TestTimeProvider TimeProvider { get; private set; } = new(); + + /// + /// Returns the DbContext instance for the test. This can be used to seed the database with test data. + /// + /// AliasServerDbContext instance. + public AliasServerDbContext GetDbContext() + { + if (_dbContext != null) + { + return _dbContext; + } + + _dbContext = _dbContextFactory.CreateDbContext(); + return _dbContext; + } + + /// + /// Disposes the DbConnection instance and drops the temporary database. + /// + /// Task. + public override async ValueTask DisposeAsync() + { + if (_dbContext != null) + { + await _dbContext.DisposeAsync(); + _dbContext = null; + } + + if (!string.IsNullOrEmpty(_tempDbName)) + { + // Create a connection to 'postgres' database to drop the test database + using var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password"); + await conn.OpenAsync(); + + // First terminate existing connections + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = '{_tempDbName}'; + """; + await cmd.ExecuteNonQueryAsync(); + } + + // Then drop the database in a separate command + using (var cmd = conn.CreateCommand()) + { + cmd.CommandText = $""" + DROP DATABASE IF EXISTS "{_tempDbName}"; + """; + await cmd.ExecuteNonQueryAsync(); + } + } + + GC.SuppressFinalize(this); + await base.DisposeAsync(); + } + + /// + protected override IHost CreateHost(IHostBuilder builder) + { + builder.ConfigureWebHost(webHostBuilder => + { + webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port)); + webHostBuilder.ConfigureServices(s => s.AddSingleton()); + }); + + var host = base.CreateHost(builder); + + // Get the DbContextFactory instance and store it for later use during tests. + _dbContextFactory = host.Services.GetRequiredService(); + + return host; + } + + /// + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; + SetEnvironmentVariables(); + + builder.ConfigureServices(services => + { + RemoveExistingRegistrations(services); + AddNewRegistrations(services); + }); + } + + /// + /// Adds new service registrations. + /// + /// The to modify. + protected abstract void AddNewRegistrations(IServiceCollection services); + + /// + /// Removes existing service registrations. + /// + /// The to modify. + protected abstract void RemoveExistingRegistrations(IServiceCollection services); + + /// + /// Sets the required environment variables for testing. + /// + private void SetEnvironmentVariables() + { + Environment.SetEnvironmentVariable("ConnectionStrings__AliasServerDbContext", "Host=localhost;Port=5433;Database=" + _tempDbName + ";Username=aliasvault;Password=password"); + Environment.SetEnvironmentVariable("JWT_KEY", "12345678901234567890123456789012"); + Environment.SetEnvironmentVariable("DATA_PROTECTION_CERT_PASS", "Development"); + Environment.SetEnvironmentVariable("PUBLIC_REGISTRATION_ENABLED", "true"); + Environment.SetEnvironmentVariable("ADMIN_PASSWORD_HASH", "AQAAAAIAAYagAAAAEKWfKfa2gh9Z72vjAlnNP1xlME7FsunRznzyrfqFte40FToufRwa3kX8wwDwnEXZag=="); + Environment.SetEnvironmentVariable("ADMIN_PASSWORD_GENERATED", "2024-01-01T00:00:00Z"); + } +} diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs index 370ecb25..44f5b17b 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationAdminFactoryFixture.cs @@ -7,135 +7,27 @@ namespace AliasVault.E2ETests.Infrastructure; -using AliasServerDb; using AliasVault.Admin.Services; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Mvc.Testing; +using AliasVault.E2ETests.Infrastructure.Abstracts; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Npgsql; /// /// Admin web application factory fixture for integration tests. /// /// The entry point. -public class WebApplicationAdminFactoryFixture : WebApplicationFactory +public class WebApplicationAdminFactoryFixture : WebApplicationFactoryFixture where TEntryPoint : class { - /// - /// The DbContextFactory instance that is created for the test. - /// - private IAliasServerDbContextFactory _dbContextFactory = null!; - - /// - /// The cached DbContext instance that can be used during the test. - /// - private AliasServerDbContext? _dbContext; - - /// - /// The name of the temporary test database. - /// - private string? _tempDbName; - /// /// Gets or sets the port the web application kestrel host will listen on. /// - public int Port { get; set; } = 5003; - - /// - /// Returns the DbContext instance for the test. This can be used to seed the database with test data. - /// - /// AliasServerDbContext instance. - public AliasServerDbContext GetDbContext() - { - if (_dbContext != null) - { - return _dbContext; - } - - _dbContext = _dbContextFactory.CreateDbContext(); - return _dbContext; - } - - /// - /// Disposes the DbConnection instance and drops the temporary database. - /// - /// Task. - public override async ValueTask DisposeAsync() - { - if (_dbContext != null) - { - await _dbContext.DisposeAsync(); - _dbContext = null; - } - - if (!string.IsNullOrEmpty(_tempDbName)) - { - // Create a connection to 'postgres' database to drop the test database - using var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password"); - await conn.OpenAsync(); - - // First terminate existing connections - using (var cmd = conn.CreateCommand()) - { - cmd.CommandText = $""" - SELECT pg_terminate_backend(pid) - FROM pg_stat_activity - WHERE datname = '{_tempDbName}'; - """; - await cmd.ExecuteNonQueryAsync(); - } - - // Then drop the database in a separate command - using (var cmd = conn.CreateCommand()) - { - cmd.CommandText = $""" - DROP DATABASE IF EXISTS "{_tempDbName}"; - """; - await cmd.ExecuteNonQueryAsync(); - } - } - - GC.SuppressFinalize(this); - await base.DisposeAsync(); - } - - /// - protected override IHost CreateHost(IHostBuilder builder) - { - builder.ConfigureWebHost(webHostBuilder => - { - webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port)); - webHostBuilder.ConfigureServices(s => s.AddSingleton()); - }); - - var host = base.CreateHost(builder); - - // Get the DbContextFactory instance and store it for later use during tests. - _dbContextFactory = host.Services.GetRequiredService(); - - return host; - } - - /// - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; - SetEnvironmentVariables(); - - builder.ConfigureServices(services => - { - RemoveExistingRegistrations(services); - AddNewRegistrations(services); - }); - } + public override int Port { get; set; } = 5003; /// /// Removes existing service registrations. /// /// The to modify. - private static void RemoveExistingRegistrations(IServiceCollection services) + protected override void RemoveExistingRegistrations(IServiceCollection services) { var descriptorsToRemove = services.Where(d => d.ServiceType == typeof(VersionedContentService)).ToList(); @@ -146,22 +38,11 @@ private static void RemoveExistingRegistrations(IServiceCollection services) } } - /// - /// Sets the required environment variables for testing. - /// - private void SetEnvironmentVariables() - { - Environment.SetEnvironmentVariable("ConnectionStrings__AliasServerDbContext", "Host=postgres;Database=" + _tempDbName + ";Username=aliasvault;Password=password"); - Environment.SetEnvironmentVariable("ADMIN_PASSWORD_HASH", "AQAAAAIAAYagAAAAEKWfKfa2gh9Z72vjAlnNP1xlME7FsunRznzyrfqFte40FToufRwa3kX8wwDwnEXZag=="); - Environment.SetEnvironmentVariable("ADMIN_PASSWORD_GENERATED", "2024-01-01T00:00:00Z"); - Environment.SetEnvironmentVariable("DATA_PROTECTION_CERT_PASS", "Development"); - } - /// /// Adds new service registrations. /// /// The to modify. - private void AddNewRegistrations(IServiceCollection services) + protected override void AddNewRegistrations(IServiceCollection services) { // Add the VersionedContentService services.AddSingleton(new VersionedContentService("../../../../../AliasVault.Admin/wwwroot")); diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs index b48a6697..6456b299 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs @@ -7,140 +7,27 @@ namespace AliasVault.E2ETests.Infrastructure; -using AliasServerDb; +using AliasVault.E2ETests.Infrastructure.Abstracts; using AliasVault.Shared.Providers.Time; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Npgsql; /// /// API web application factory fixture for integration tests. /// /// The entry point. -public class WebApplicationApiFactoryFixture : WebApplicationFactory +public class WebApplicationApiFactoryFixture : WebApplicationFactoryFixture where TEntryPoint : class { - /// - /// The DbContextFactory instance that is created for the test. - /// - private IAliasServerDbContextFactory _dbContextFactory = null!; - - /// - /// The cached DbContext instance that can be used during the test. - /// - private AliasServerDbContext? _dbContext; - - /// - /// The name of the temporary test database. - /// - private string? _tempDbName; - /// /// Gets or sets the port the web application kestrel host will listen on. /// - public int Port { get; set; } = 5001; - - /// - /// Gets the time provider instance for mutating the current time in tests. - /// - public TestTimeProvider TimeProvider { get; private set; } = new(); - - /// - /// Returns the DbContext instance for the test. This can be used to seed the database with test data. - /// - /// AliasServerDbContext instance. - public AliasServerDbContext GetDbContext() - { - if (_dbContext != null) - { - return _dbContext; - } - - _dbContext = _dbContextFactory.CreateDbContext(); - return _dbContext; - } - - /// - /// Disposes the DbConnection instance and drops the temporary database. - /// - /// Task. - public override async ValueTask DisposeAsync() - { - if (_dbContext != null) - { - await _dbContext.DisposeAsync(); - _dbContext = null; - } - - if (!string.IsNullOrEmpty(_tempDbName)) - { - // Create a connection to 'postgres' database to drop the test database - using var conn = new NpgsqlConnection("Host=localhost;Port=5433;Database=postgres;Username=aliasvault;Password=password"); - await conn.OpenAsync(); - - // First terminate existing connections - using (var cmd = conn.CreateCommand()) - { - cmd.CommandText = $""" - SELECT pg_terminate_backend(pid) - FROM pg_stat_activity - WHERE datname = '{_tempDbName}'; - """; - await cmd.ExecuteNonQueryAsync(); - } - - // Then drop the database in a separate command - using (var cmd = conn.CreateCommand()) - { - cmd.CommandText = $""" - DROP DATABASE IF EXISTS "{_tempDbName}"; - """; - await cmd.ExecuteNonQueryAsync(); - } - } - - GC.SuppressFinalize(this); - await base.DisposeAsync(); - } - - /// - protected override IHost CreateHost(IHostBuilder builder) - { - builder.ConfigureWebHost(webHostBuilder => - { - webHostBuilder.UseKestrel(opt => opt.ListenLocalhost(Port)); - webHostBuilder.ConfigureServices(s => s.AddSingleton()); - }); - - var host = base.CreateHost(builder); - - // Get the DbContextFactory instance and store it for later use during tests. - _dbContextFactory = host.Services.GetRequiredService(); - - return host; - } - - /// - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - _tempDbName = $"aliasdb_test_{Guid.NewGuid()}"; - SetEnvironmentVariables(); - - builder.ConfigureServices(services => - { - RemoveExistingRegistrations(services); - AddNewRegistrations(services); - }); - } + public override int Port { get; set; } = 5001; /// /// Removes existing service registrations. /// /// The to modify. - private static void RemoveExistingRegistrations(IServiceCollection services) + protected override void RemoveExistingRegistrations(IServiceCollection services) { var descriptorsToRemove = services.Where(d => d.ServiceType == typeof(ITimeProvider)).ToList(); @@ -151,22 +38,11 @@ private static void RemoveExistingRegistrations(IServiceCollection services) } } - /// - /// Sets the required environment variables for testing. - /// - private void SetEnvironmentVariables() - { - Environment.SetEnvironmentVariable("ConnectionStrings__AliasServerDbContext", "Host=postgres;Database=" + _tempDbName + ";Username=aliasvault;Password=password"); - Environment.SetEnvironmentVariable("JWT_KEY", "12345678901234567890123456789012"); - Environment.SetEnvironmentVariable("DATA_PROTECTION_CERT_PASS", "Development"); - Environment.SetEnvironmentVariable("PUBLIC_REGISTRATION_ENABLED", "true"); - } - /// /// Adds new service registrations. /// /// The to modify. - private void AddNewRegistrations(IServiceCollection services) + protected override void AddNewRegistrations(IServiceCollection services) { // Add TestTimeProvider services.AddSingleton(TimeProvider); From f6e2648a53cf0eacee4580c271607876bca518ff Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 20:16:59 +0100 Subject: [PATCH 26/62] Update GitHub actions for postgresql (#190) --- .github/workflows/dotnet-e2e-admin-tests.yml | 8 +++++ .github/workflows/dotnet-e2e-client-tests.yml | 8 +++++ .github/workflows/dotnet-e2e-misc-tests.yml | 8 +++++ .../workflows/dotnet-integration-tests.yml | 8 +++++ install.sh | 32 +++++++++++++++++++ .../20241222185633_InitialMigration.cs | 3 +- 6 files changed, 66 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-e2e-admin-tests.yml b/.github/workflows/dotnet-e2e-admin-tests.yml index f807b817..70ab08b9 100644 --- a/.github/workflows/dotnet-e2e-admin-tests.yml +++ b/.github/workflows/dotnet-e2e-admin-tests.yml @@ -25,6 +25,14 @@ jobs: - name: Build run: dotnet build + - name: Start dev database + run: ./install.sh configure-dev-db start + + - name: Wait for database to be up + run: | + # Wait for a few seconds + sleep 5 + - name: Ensure browsers are installed run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps diff --git a/.github/workflows/dotnet-e2e-client-tests.yml b/.github/workflows/dotnet-e2e-client-tests.yml index 2bb3f747..b10b7a18 100644 --- a/.github/workflows/dotnet-e2e-client-tests.yml +++ b/.github/workflows/dotnet-e2e-client-tests.yml @@ -29,6 +29,14 @@ jobs: - name: Build run: dotnet build + - name: Start dev database + run: ./install.sh configure-dev-db start + + - name: Wait for database to be up + run: | + # Wait for a few seconds + sleep 5 + - name: Ensure browsers are installed run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps diff --git a/.github/workflows/dotnet-e2e-misc-tests.yml b/.github/workflows/dotnet-e2e-misc-tests.yml index 5418f16b..d0d132a5 100644 --- a/.github/workflows/dotnet-e2e-misc-tests.yml +++ b/.github/workflows/dotnet-e2e-misc-tests.yml @@ -25,6 +25,14 @@ jobs: - name: Build run: dotnet build + - name: Start dev database + run: ./install.sh configure-dev-db start + + - name: Wait for database to be up + run: | + # Wait for a few seconds + sleep 5 + - name: Ensure browsers are installed run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps diff --git a/.github/workflows/dotnet-integration-tests.yml b/.github/workflows/dotnet-integration-tests.yml index c6242b5c..add403b9 100644 --- a/.github/workflows/dotnet-integration-tests.yml +++ b/.github/workflows/dotnet-integration-tests.yml @@ -25,5 +25,13 @@ jobs: - name: Build run: dotnet build + - name: Start dev database + run: ./install.sh configure-dev-db start + + - name: Wait for database to be up + run: | + # Wait for a few seconds + sleep 5 + - name: Run integration tests run: dotnet test src/Tests/AliasVault.IntegrationTests --no-build --verbosity normal diff --git a/install.sh b/install.sh index d2cfe095..a11a3850 100755 --- a/install.sh +++ b/install.sh @@ -130,6 +130,11 @@ parse_args() { configure-dev-db|dev-db) COMMAND="configure-dev-db" shift + # Check for direct option argument + if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then + COMMAND_ARG="$1" + shift + fi ;; --help) show_usage @@ -1456,6 +1461,33 @@ configure_dev_database() { curl -sSf "${GITHUB_RAW_URL_REPO_BRANCH}/docker-compose.dev.yml" -o "docker-compose.dev.yml" fi + # Check if direct option was provided + if [ -n "$COMMAND_ARG" ]; then + case $COMMAND_ARG in + 1|start) + if docker compose -f docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then + printf "${YELLOW}> Development database is already running.${NC}\n" + else + printf "${CYAN}> Starting development database...${NC}\n" + docker compose -p aliasvault-dev -f docker-compose.dev.yml up -d + printf "${GREEN}> Development database started successfully.${NC}\n" + fi + print_dev_db_details + return + ;; + 0|stop) + if ! docker compose -f docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then + printf "${YELLOW}> Development database is already stopped.${NC}\n" + else + printf "${CYAN}> Stopping development database...${NC}\n" + docker compose -p aliasvault-dev -f docker-compose.dev.yml down + printf "${GREEN}> Development database stopped successfully.${NC}\n" + fi + return + ;; + esac + fi + # Check current status if docker compose -f docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then DEV_DB_STATUS="running" diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.cs index 38900340..199fa00c 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.cs @@ -1,4 +1,5 @@ -using System; +// +using System; using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; From 6a0699318c8a6b2ddd76031f33c151809a4719b6 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 23 Dec 2024 21:41:25 +0100 Subject: [PATCH 27/62] Add sqlite migrations to be in sync with postgresql model (#190) --- ...3201909_IncludePostgresqlFixes.Designer.cs | 886 ++++++++++++++++++ .../20241223201909_IncludePostgresqlFixes.cs | 54 ++ ...AliasServerDbContextSqliteModelSnapshot.cs | 6 +- 3 files changed, 944 insertions(+), 2 deletions(-) create mode 100644 src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.Designer.cs create mode 100644 src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.cs diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.Designer.cs new file mode 100644 index 00000000..e510fb1c --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.Designer.cs @@ -0,0 +1,886 @@ +// +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.SqliteMigrations +{ + [DbContext(typeof(AliasServerDbContextSqlite))] + [Migration("20241223201909_IncludePostgresqlFixes")] + partial class IncludePostgresqlFixes + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true); + + modelBuilder.Entity("AliasServerDb.AdminRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AdminRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AdminUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LastPasswordChanged") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AdminUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Blocked") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT"); + + b.Property("PasswordChangedAt") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeviceIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("ExpireDate") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("PreviousTokenValue") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AliasVaultUserRefreshTokens"); + }); + + modelBuilder.Entity("AliasServerDb.AuthLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalInfo") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Browser") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("FailureReason") + .HasColumnType("INTEGER"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("IsSuccess") + .HasColumnType("INTEGER"); + + b.Property("IsSuspiciousActivity") + .HasColumnType("INTEGER"); + + b.Property("OperatingSystem") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("RequestPath") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.Property("UserAgent") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EventType" }, "IX_EventType"); + + b.HasIndex(new[] { "IpAddress" }, "IX_IpAddress"); + + b.HasIndex(new[] { "Timestamp" }, "IX_Timestamp"); + + b.HasIndex(new[] { "Username", "IsSuccess", "Timestamp" }, "IX_Username_IsSuccess_Timestamp") + .IsDescending(false, false, true); + + b.HasIndex(new[] { "Username", "Timestamp" }, "IX_Username_Timestamp") + .IsDescending(false, true); + + b.ToTable("AuthLogs"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DateSystem") + .HasColumnType("TEXT"); + + b.Property("EncryptedSymmetricKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("From") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FromDomain") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FromLocal") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MessageHtml") + .HasColumnType("TEXT"); + + b.Property("MessagePlain") + .HasColumnType("TEXT"); + + b.Property("MessagePreview") + .HasColumnType("TEXT"); + + b.Property("MessageSource") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PushNotificationSent") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("To") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToDomain") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToLocal") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserEncryptionKeyId") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("DateSystem"); + + b.HasIndex("PushNotificationSent"); + + b.HasIndex("ToLocal"); + + b.HasIndex("UserEncryptionKeyId"); + + b.HasIndex("Visible"); + + b.ToTable("Emails"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bytes") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("EmailId") + .HasColumnType("INTEGER"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Filesize") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EmailId"); + + b.ToTable("EmailAttachments"); + }); + + modelBuilder.Entity("AliasServerDb.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Application") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Exception") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("LogEvent") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("LogEvent"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MessageTemplate") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SourceContext") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TimeStamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Application"); + + b.HasIndex("TimeStamp"); + + b.ToTable("Logs", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.ServerSetting", b => + { + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("AliasServerDb.TaskRunnerJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EndTime") + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT"); + + b.Property("IsOnDemand") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RunDate") + .HasColumnType("TEXT"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TaskRunnerJobs"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AddressDomain") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AddressLocal") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Address") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("UserEmailClaims"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserEncryptionKeys"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CredentialsCount") + .HasColumnType("INTEGER"); + + b.Property("EmailClaimsCount") + .HasColumnType("INTEGER"); + + b.Property("EncryptionSettings") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EncryptionType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("RevisionNumber") + .HasColumnType("INTEGER"); + + b.Property("Salt") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("VaultBlob") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Verifier") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("AliasVault.WorkerStatus.Database.WorkerServiceStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CurrentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DesiredStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Heartbeat") + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WorkerServiceStatuses"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FriendlyName") + .HasColumnType("TEXT"); + + b.Property("Xml") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey") + .WithMany("Emails") + .HasForeignKey("UserEncryptionKeyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EncryptionKey"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.HasOne("AliasServerDb.Email", "Email") + .WithMany("Attachments") + .HasForeignKey("EmailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Email"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EmailClaims") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EncryptionKeys") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("Vaults") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Navigation("EmailClaims"); + + b.Navigation("EncryptionKeys"); + + b.Navigation("Vaults"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Navigation("Emails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.cs new file mode 100644 index 00000000..f86f0d31 --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AliasServerDb.Migrations.SqliteMigrations +{ + /// + public partial class IncludePostgresqlFixes : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ServiceName", + table: "WorkerServiceStatuses", + type: "TEXT", + maxLength: 255, + nullable: false, + oldClrType: typeof(string), + oldType: "varchar", + oldMaxLength: 255); + + migrationBuilder.AlterColumn( + name: "EventType", + table: "AuthLogs", + type: "INTEGER", + nullable: false, + oldClrType: typeof(int), + oldType: "nvarchar(50)"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "ServiceName", + table: "WorkerServiceStatuses", + type: "varchar", + maxLength: 255, + nullable: false, + oldClrType: typeof(string), + oldType: "TEXT", + oldMaxLength: 255); + + migrationBuilder.AlterColumn( + name: "EventType", + table: "AuthLogs", + type: "nvarchar(50)", + nullable: false, + oldClrType: typeof(int), + oldType: "INTEGER"); + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs index 517eca09..6d113093 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs @@ -244,7 +244,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("EventType") - .HasColumnType("nvarchar(50)"); + .HasColumnType("INTEGER"); b.Property("FailureReason") .HasColumnType("INTEGER"); @@ -453,6 +453,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("SourceContext") .IsRequired() + .HasMaxLength(255) .HasColumnType("TEXT"); b.Property("TimeStamp") @@ -504,6 +505,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() + .HasMaxLength(50) .HasColumnType("TEXT"); b.Property("RunDate") @@ -678,7 +680,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ServiceName") .IsRequired() .HasMaxLength(255) - .HasColumnType("varchar"); + .HasColumnType("TEXT"); b.HasKey("Id"); From 6b59200df273478ff3c8e26615642c469b69edf0 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 11:59:37 +0100 Subject: [PATCH 28/62] Fix migrations (#190) --- .../20240526135300_InitialMigration.Designer.cs | 2 +- .../SqliteMigrations/20240527082514_BasicEntities.Designer.cs | 2 +- .../SqliteMigrations/20240529140248_LoginUserId.Designer.cs | 2 +- .../SqliteMigrations/20240531142952_AddServiceLogo.Designer.cs | 2 +- .../20240603175245_AddAspNetUserRefreshTokens.Designer.cs | 2 +- .../20240616200303_ChangeColumnDefinitions.Designer.cs | 2 +- .../20240621092501_AddAliasVaultUser.Designer.cs | 2 +- .../SqliteMigrations/20240624125214_AddVaultsTable.Designer.cs | 2 +- .../20240701104445_RemoveClientTables.Designer.cs | 2 +- .../20240708113743_AddVaultVersionColumn.Designer.cs | 2 +- .../SqliteMigrations/20240720151458_AddEmailTables.Designer.cs | 2 +- .../20240720211546_AddAdminUsersTable.Designer.cs | 2 +- .../20240722144205_AddAdminUserPasswordSetDate.Designer.cs | 2 +- .../SqliteMigrations/20240723194939_CreateLogTable.Designer.cs | 2 +- .../20240725202058_WorkerStatusTable.Designer.cs | 2 +- .../20240728110837_AddMetadataColumns.Designer.cs | 2 +- .../20240729150925_AddEncryptionKeyTables.Designer.cs | 2 +- .../20240821164021_AddDataProtectionDatabaseTable.Designer.cs | 2 +- .../SqliteMigrations/20240821204113_UpdateLogTable.Designer.cs | 2 +- .../SqliteMigrations/20240830190840_AddAuthLogTable.Designer.cs | 2 +- .../20240902134755_MoveSrpColsToVaultTable.Designer.cs | 2 +- .../20240902172644_RemoveSrpColsFromUserTable.Designer.cs | 2 +- .../20240906092833_AddVaultStatisticsColumns.Designer.cs | 2 +- .../20240911174159_AddVaultEncryptionSettings.Designer.cs | 2 +- .../20240914150600_AddRefreshTokenIpAddress.Designer.cs | 2 +- .../20240916110323_AddVaultRevisionNumber.Designer.cs | 2 +- .../20240917210632_SetDefaultRevisionNumber.Designer.cs | 2 +- .../20241007153012_DatetimeOffsetToDateTime.Designer.cs | 2 +- ...0241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs | 2 +- .../20241204121218_AddServerSettingsTable.Designer.cs | 2 +- .../20241215131807_AddTaskRunnerJobTable.Designer.cs | 2 +- .../20241220164855_AddUserBlockedStatus.Designer.cs | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.Designer.cs index 28966b43..aab7ff0b 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240526135300_InitialMigration.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240526135300_InitialMigration")] partial class InitialMigration { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.Designer.cs index 2f662aba..860dc61b 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240527082514_BasicEntities.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240527082514_BasicEntities")] partial class BasicEntities { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.Designer.cs index 9c77a10a..65aed416 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240529140248_LoginUserId.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240529140248_LoginUserId")] partial class LoginUserId { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.Designer.cs index e42ee77a..c1c137ba 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240531142952_AddServiceLogo.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240531142952_AddServiceLogo")] partial class AddServiceLogo { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs index d7499a59..1a63b693 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240603175245_AddAspNetUserRefreshTokens.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240603175245_AddAspNetUserRefreshTokens")] partial class AddAspNetUserRefreshTokens { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.Designer.cs index c2c13bcd..debf65a4 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240616200303_ChangeColumnDefinitions.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240616200303_ChangeColumnDefinitions")] partial class ChangeColumnDefinitions { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.Designer.cs index c36a4632..b4723c46 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240621092501_AddAliasVaultUser.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240621092501_AddAliasVaultUser")] partial class AddAliasVaultUser { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.Designer.cs index f2f8528a..ff9a2965 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240624125214_AddVaultsTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240624125214_AddVaultsTable")] partial class AddVaultsTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.Designer.cs index 28bfda56..e4e7185d 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240701104445_RemoveClientTables.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240701104445_RemoveClientTables")] partial class RemoveClientTables { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.Designer.cs index 208ea92e..ee85470f 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240708113743_AddVaultVersionColumn.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240708113743_AddVaultVersionColumn")] partial class AddVaultVersionColumn { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.Designer.cs index 869a7d19..9efc7da9 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720151458_AddEmailTables.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240718151458_AddEmailTables")] partial class AddEmailTables { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.Designer.cs index 64b37311..c1dc3cb5 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240720211546_AddAdminUsersTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240720211546_AddAdminUsersTable")] partial class AddAdminUsersTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs index e9070ae2..d07bc386 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240722144205_AddAdminUserPasswordSetDate.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240722144205_AddAdminUserPasswordSetDate")] partial class AddAdminUserPasswordSetDate { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.Designer.cs index 3ebc1741..253ff089 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240723194939_CreateLogTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240723194939_CreateLogTable")] partial class CreateLogTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.Designer.cs index c46b2d31..e8f7bd69 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240725202058_WorkerStatusTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240725202058_WorkerStatusTable")] partial class WorkerStatusTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.Designer.cs index f110bb43..9665b85d 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240728110837_AddMetadataColumns.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240728110837_AddMetadataColumns")] partial class AddMetadataColumns { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.Designer.cs index e6ec564d..150763bb 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240729150925_AddEncryptionKeyTables.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240729150925_AddEncryptionKeyTables")] partial class AddEncryptionKeyTables { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs index f3beef55..af2e8f23 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821164021_AddDataProtectionDatabaseTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240821164021_AddDataProtectionDatabaseTable")] partial class AddDataProtectionDatabaseTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.Designer.cs index afc0180c..8d6457e9 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240821204113_UpdateLogTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240821204113_UpdateLogTable")] partial class UpdateLogTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.Designer.cs index bbdcb6e3..19ba838b 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240830190840_AddAuthLogTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240830190840_AddAuthLogTable")] partial class AddAuthLogTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs index 72979521..a68926d5 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902134755_MoveSrpColsToVaultTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240902134755_MoveSrpColsToVaultTable")] partial class MoveSrpColsToVaultTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs index 4ada3b91..39603316 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240902172644_RemoveSrpColsFromUserTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240902172644_RemoveSrpColsFromUserTable")] partial class RemoveSrpColsFromUserTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.Designer.cs index 5a00bdb2..554a81c6 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240906092833_AddVaultStatisticsColumns.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240906092833_AddVaultStatisticsColumns")] partial class AddVaultStatisticsColumns { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.Designer.cs index c57be6e2..87895af1 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240911174159_AddVaultEncryptionSettings.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240911174159_AddVaultEncryptionSettings")] partial class AddVaultEncryptionSettings { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs index 218fd9be..2828e4bf 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240914150600_AddRefreshTokenIpAddress.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240914150600_AddRefreshTokenIpAddress")] partial class AddRefreshTokenIpAddress { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.Designer.cs index f92eefbf..0f0654bb 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240916110323_AddVaultRevisionNumber.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240916110323_AddVaultRevisionNumber")] partial class AddVaultRevisionNumber { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.Designer.cs index a588bc94..51e38b33 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20240917210632_SetDefaultRevisionNumber.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20240917210632_SetDefaultRevisionNumber")] partial class SetDefaultRevisionNumber { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs index 2d0b3efe..1ff68d65 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241007153012_DatetimeOffsetToDateTime.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20241007153012_DatetimeOffsetToDateTime")] partial class DatetimeOffsetToDateTime { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs index 2de5db5f..14082f61 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241107205118_PreserveEmailClaimsForDeletedUsers.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20241107105118_PreserveEmailClaimsForDeletedUsers")] partial class PreserveEmailClaimsForDeletedUsers { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.Designer.cs index f47f01df..c5a0ead3 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241204121218_AddServerSettingsTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20241204121218_AddServerSettingsTable")] partial class AddServerSettingsTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.Designer.cs index c2237aea..f466f898 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241215131807_AddTaskRunnerJobTable.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20241215131807_AddTaskRunnerJobTable")] partial class AddTaskRunnerJobTable { diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.Designer.cs index 877de97c..6fbcec5f 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241220164855_AddUserBlockedStatus.Designer.cs @@ -10,7 +10,7 @@ namespace AliasServerDb.Migrations.SqliteMigrations { - [DbContext(typeof(AliasServerDbContext))] + [DbContext(typeof(AliasServerDbContextSqlite))] [Migration("20241220164855_AddUserBlockedStatus")] partial class AddUserBlockedStatus { From 7a62ddcf6ae1ea81ba65924342b9f9d917f9bcbd Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 12:39:57 +0100 Subject: [PATCH 29/62] Enable postgresql legacy timestamp behavior (#190) --- .../AliasServerDbContextPostgresql.cs | 2 + ...20241224113431_LegacyTimestamp.Designer.cs | 908 ++++++++++++++++++ .../20241224113431_LegacyTimestamp.cs | 361 +++++++ ...sServerDbContextPostgresqlModelSnapshot.cs | 42 +- 4 files changed, 1292 insertions(+), 21 deletions(-) create mode 100644 src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.Designer.cs create mode 100644 src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.cs diff --git a/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs b/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs index 35281f26..ce4c8834 100644 --- a/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs +++ b/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs @@ -37,6 +37,8 @@ public AliasServerDbContextPostgresql(DbContextOptions opt /// DbContextOptionsBuilder instance. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + if (optionsBuilder.IsConfigured) { return; diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.Designer.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.Designer.cs new file mode 100644 index 00000000..33d5c003 --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.Designer.cs @@ -0,0 +1,908 @@ +// +using System; +using AliasServerDb; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace AliasServerDb.Migrations.PostgresqlMigrations +{ + [DbContext(typeof(AliasServerDbContextPostgresql))] + [Migration("20241224113431_LegacyTimestamp")] + partial class LegacyTimestamp + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AliasServerDb.AdminRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("NormalizedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AdminRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AdminUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastPasswordChanged") + .HasColumnType("timestamp without time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AdminUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("NormalizedName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Blocked") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasColumnType("text"); + + b.Property("NormalizedUserName") + .HasColumnType("text"); + + b.Property("PasswordChangedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("UserName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("DeviceIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ExpireDate") + .HasMaxLength(255) + .HasColumnType("timestamp without time zone"); + + b.Property("IpAddress") + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("PreviousTokenValue") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AliasVaultUserRefreshTokens"); + }); + + modelBuilder.Entity("AliasServerDb.AuthLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdditionalInfo") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Browser") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DeviceType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("FailureReason") + .HasColumnType("integer"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsSuccess") + .HasColumnType("boolean"); + + b.Property("IsSuspiciousActivity") + .HasColumnType("boolean"); + + b.Property("OperatingSystem") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("RequestPath") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Timestamp") + .HasColumnType("timestamp without time zone"); + + b.Property("UserAgent") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EventType" }, "IX_EventType"); + + b.HasIndex(new[] { "IpAddress" }, "IX_IpAddress"); + + b.HasIndex(new[] { "Timestamp" }, "IX_Timestamp"); + + b.HasIndex(new[] { "Username", "IsSuccess", "Timestamp" }, "IX_Username_IsSuccess_Timestamp") + .IsDescending(false, false, true); + + b.HasIndex(new[] { "Username", "Timestamp" }, "IX_Username_Timestamp") + .IsDescending(false, true); + + b.ToTable("AuthLogs"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("timestamp without time zone"); + + b.Property("DateSystem") + .HasColumnType("timestamp without time zone"); + + b.Property("EncryptedSymmetricKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("From") + .IsRequired() + .HasColumnType("text"); + + b.Property("FromDomain") + .IsRequired() + .HasColumnType("text"); + + b.Property("FromLocal") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageHtml") + .HasColumnType("text"); + + b.Property("MessagePlain") + .HasColumnType("text"); + + b.Property("MessagePreview") + .HasColumnType("text"); + + b.Property("MessageSource") + .IsRequired() + .HasColumnType("text"); + + b.Property("PushNotificationSent") + .HasColumnType("boolean"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("To") + .IsRequired() + .HasColumnType("text"); + + b.Property("ToDomain") + .IsRequired() + .HasColumnType("text"); + + b.Property("ToLocal") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserEncryptionKeyId") + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("Visible") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("DateSystem"); + + b.HasIndex("PushNotificationSent"); + + b.HasIndex("ToLocal"); + + b.HasIndex("UserEncryptionKeyId"); + + b.HasIndex("Visible"); + + b.ToTable("Emails"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Bytes") + .IsRequired() + .HasColumnType("bytea"); + + b.Property("Date") + .HasColumnType("timestamp without time zone"); + + b.Property("EmailId") + .HasColumnType("integer"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("text"); + + b.Property("Filesize") + .HasColumnType("integer"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("EmailId"); + + b.ToTable("EmailAttachments"); + }); + + modelBuilder.Entity("AliasServerDb.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Application") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Exception") + .IsRequired() + .HasColumnType("text"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("LogEvent") + .IsRequired() + .HasColumnType("text") + .HasColumnName("LogEvent"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageTemplate") + .IsRequired() + .HasColumnType("text"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceContext") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TimeStamp") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("Application"); + + b.HasIndex("TimeStamp"); + + b.ToTable("Logs", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.ServerSetting", b => + { + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Key"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("AliasServerDb.TaskRunnerJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EndTime") + .HasColumnType("time without time zone"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("IsOnDemand") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RunDate") + .HasColumnType("timestamp without time zone"); + + b.Property("StartTime") + .HasColumnType("time without time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("TaskRunnerJobs"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AddressDomain") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AddressLocal") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Address") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("UserEmailClaims"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserEncryptionKeys"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CredentialsCount") + .HasColumnType("integer"); + + b.Property("EmailClaimsCount") + .HasColumnType("integer"); + + b.Property("EncryptionSettings") + .IsRequired() + .HasColumnType("text"); + + b.Property("EncryptionType") + .IsRequired() + .HasColumnType("text"); + + b.Property("FileSize") + .HasColumnType("integer"); + + b.Property("RevisionNumber") + .HasColumnType("bigint"); + + b.Property("Salt") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("VaultBlob") + .IsRequired() + .HasColumnType("text"); + + b.Property("Verifier") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("AliasVault.WorkerStatus.Database.WorkerServiceStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DesiredStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Heartbeat") + .HasColumnType("timestamp without time zone"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("WorkerServiceStatuses"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey") + .WithMany("Emails") + .HasForeignKey("UserEncryptionKeyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EncryptionKey"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.HasOne("AliasServerDb.Email", "Email") + .WithMany("Attachments") + .HasForeignKey("EmailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Email"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EmailClaims") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EncryptionKeys") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("Vaults") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Navigation("EmailClaims"); + + b.Navigation("EncryptionKeys"); + + b.Navigation("Vaults"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Navigation("Emails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.cs new file mode 100644 index 00000000..e887f16c --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.cs @@ -0,0 +1,361 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AliasServerDb.Migrations.PostgresqlMigrations +{ + /// + public partial class LegacyTimestamp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Heartbeat", + table: "WorkerServiceStatuses", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "Vaults", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "Vaults", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "UserEncryptionKeys", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "UserEncryptionKeys", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "UserEmailClaims", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "UserEmailClaims", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "RunDate", + table: "TaskRunnerJobs", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "ServerSettings", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "ServerSettings", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "TimeStamp", + table: "Logs", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "DateSystem", + table: "Emails", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "Date", + table: "Emails", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "Date", + table: "EmailAttachments", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "Timestamp", + table: "AuthLogs", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "AliasVaultUsers", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "PasswordChangedAt", + table: "AliasVaultUsers", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "AliasVaultUsers", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "ExpireDate", + table: "AliasVaultUserRefreshTokens", + type: "timestamp without time zone", + maxLength: 255, + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldMaxLength: 255); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "AliasVaultUserRefreshTokens", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "LastPasswordChanged", + table: "AdminUsers", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Heartbeat", + table: "WorkerServiceStatuses", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "Vaults", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "Vaults", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "UserEncryptionKeys", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "UserEncryptionKeys", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "UserEmailClaims", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "UserEmailClaims", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "RunDate", + table: "TaskRunnerJobs", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "ServerSettings", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "ServerSettings", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "TimeStamp", + table: "Logs", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "DateSystem", + table: "Emails", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "Date", + table: "Emails", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "Date", + table: "EmailAttachments", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "Timestamp", + table: "AuthLogs", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "UpdatedAt", + table: "AliasVaultUsers", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "PasswordChangedAt", + table: "AliasVaultUsers", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "AliasVaultUsers", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "ExpireDate", + table: "AliasVaultUserRefreshTokens", + type: "timestamp with time zone", + maxLength: 255, + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldMaxLength: 255); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "AliasVaultUserRefreshTokens", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "LastPasswordChanged", + table: "AdminUsers", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs index c0d63ab9..5d3765cf 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs @@ -62,7 +62,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("boolean"); b.Property("LastPasswordChanged") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("LockoutEnabled") .HasColumnType("boolean"); @@ -133,7 +133,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text"); b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("Email") .HasColumnType("text"); @@ -154,7 +154,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text"); b.Property("PasswordChangedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("PasswordHash") .HasColumnType("text"); @@ -172,7 +172,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("boolean"); b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("UserName") .HasColumnType("text"); @@ -189,7 +189,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid"); b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("DeviceIdentifier") .IsRequired() @@ -198,7 +198,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ExpireDate") .HasMaxLength(255) - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("IpAddress") .HasMaxLength(45) @@ -274,7 +274,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(100)"); b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("UserAgent") .HasMaxLength(255) @@ -311,10 +311,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Date") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("DateSystem") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("EncryptedSymmetricKey") .IsRequired() @@ -401,7 +401,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("bytea"); b.Property("Date") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("EmailId") .HasColumnType("integer"); @@ -469,7 +469,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(255)"); b.Property("TimeStamp") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.HasKey("Id"); @@ -487,10 +487,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(255)"); b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("Value") .HasColumnType("text"); @@ -523,7 +523,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(50)"); b.Property("RunDate") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("StartTime") .HasColumnType("time without time zone"); @@ -558,10 +558,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(255)"); b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("UserId") .HasMaxLength(255) @@ -584,7 +584,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid"); b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("IsPrimary") .HasColumnType("boolean"); @@ -595,7 +595,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(2000)"); b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("UserId") .IsRequired() @@ -616,7 +616,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid"); b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("CredentialsCount") .HasColumnType("integer"); @@ -644,7 +644,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(100)"); b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("UserId") .IsRequired() @@ -691,7 +691,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(50)"); b.Property("Heartbeat") - .HasColumnType("timestamp with time zone"); + .HasColumnType("timestamp without time zone"); b.Property("ServiceName") .IsRequired() From 361f4b881708b94314352de897e8acfe9cf00621 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 12:53:22 +0100 Subject: [PATCH 30/62] Added migration logic from sqlite to postgresql (#190) --- .../AliasVault.InstallCli/Program.cs | 225 +++++++++++++++++- 1 file changed, 216 insertions(+), 9 deletions(-) diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index 39b7ad8c..6cb72e00 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -7,16 +7,223 @@ using AliasServerDb; using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; -if (args.Length == 0) +// Add return type for top-level statements +return await Program.Run(args); + +/// +/// Handles the migration of data between SQLite and PostgreSQL databases and password hashing utilities. +/// +public partial class Program { - Console.WriteLine("Please provide a password as an argument."); - return; -} + /// + /// Runs the program with the given arguments. + /// + /// The command-line arguments. + /// The exit code of the program. + public static async Task Run(string[] args) + { + if (args.Length == 0) + { + Console.WriteLine("Usage:"); + Console.WriteLine(" hash-password "); + Console.WriteLine(" migrate-sqlite "); + return 1; + } + + switch (args[0].ToLower()) + { + case "hash-password": + if (args.Length != 2) + { + Console.WriteLine("Usage: hash-password "); + return 1; + } + + return HashPassword(args[1]); + + case "migrate-sqlite": + if (args.Length != 2) + { + Console.WriteLine("Usage: migrate-sqlite "); + return 1; + } + + return await MigrateSqliteToPostgres(args[1]); + + default: + Console.WriteLine("Unknown command. Available commands:"); + Console.WriteLine(" hash-password "); + Console.WriteLine(" migrate-sqlite "); + return 1; + } + } + + /// + /// Hashes a password using ASP.NET Core Identity's password hasher. + /// + /// The plain text password to hash. + /// + /// Returns 0 if the password was successfully hashed and printed to console. + /// + private static int HashPassword(string password) + { + var hasher = new PasswordHasher(); + var user = new AdminUser(); + var hashedPassword = hasher.HashPassword(user, password); + Console.WriteLine(hashedPassword); + return 0; + } + + /// + /// Migrates data from a SQLite database to a PostgreSQL database. + /// + /// The file path to the source SQLite database. + /// + /// Returns 0 if migration was successful, 1 if an error occurred. + /// + /// Thrown when a migration error occurs. + private static async Task MigrateSqliteToPostgres(string sqliteDbPath) + { + try + { + if (!File.Exists(sqliteDbPath)) + { + Console.WriteLine($"Error: SQLite database not found at {sqliteDbPath}"); + return 1; + } + + Console.WriteLine($"Migrating SQLite database to PostgreSQL - start"); + + // Create connections to both databases + var sqliteConnString = $"Data Source={sqliteDbPath}"; + var pgConnString = "Host=localhost;Port=5433;Database=aliasvault;Username=aliasvault;Password=password"; + + // Create contexts + var optionsBuilderSqlite = new DbContextOptionsBuilder() + .UseSqlite(sqliteConnString); + + // Make sure sqlite is on latest version migration + Console.WriteLine("Update sqlite database to latest version..."); + await using var sqliteContext = new AliasServerDbContextSqlite(optionsBuilderSqlite.Options); + await sqliteContext.Database.MigrateAsync(); + Console.WriteLine("Updating finished."); -var password = args[0]; -var hasher = new PasswordHasher(); -var user = new AdminUser(); -var hashedPassword = hasher.HashPassword(user, password); + var optionsBuilderPg = new DbContextOptionsBuilder() + .UseNpgsql(pgConnString); -Console.WriteLine($"{hashedPassword}"); + // Make sure postgres is on latest version migration + Console.WriteLine("Update postgres database to latest version..."); + await using var pgContext = new AliasServerDbContextPostgresql(optionsBuilderPg.Options); + await pgContext.Database.EnsureDeletedAsync(); + await pgContext.Database.MigrateAsync(); + Console.WriteLine("Updating finished."); + + Console.WriteLine("Starting content migration..."); + + // First, migrate tables without foreign key dependencies + await MigrateTable(sqliteContext.AliasVaultRoles, pgContext.AliasVaultRoles, pgContext, "AliasVaultRoles"); + await MigrateTable(sqliteContext.AliasVaultUsers, pgContext.AliasVaultUsers, pgContext, "AliasVaultUsers"); + await MigrateTable(sqliteContext.ServerSettings, pgContext.ServerSettings, pgContext, "ServerSettings"); + await MigrateTable(sqliteContext.DataProtectionKeys, pgContext.DataProtectionKeys, pgContext, "DataProtectionKeys"); + await MigrateTable(sqliteContext.AuthLogs, pgContext.AuthLogs, pgContext, "AuthLogs"); + await MigrateTable(sqliteContext.AdminUsers, pgContext.AdminUsers, pgContext, "AdminUsers"); + + // Then migrate tables with foreign key dependencies + await MigrateTable(sqliteContext.AliasVaultUserRefreshTokens, pgContext.AliasVaultUserRefreshTokens, pgContext, "AliasVaultUserRefreshTokens"); + await MigrateTable(sqliteContext.UserEncryptionKeys, pgContext.UserEncryptionKeys, pgContext, "UserEncryptionKeys"); + await MigrateTable(sqliteContext.UserEmailClaims, pgContext.UserEmailClaims, pgContext, "UserEmailClaims"); + await MigrateTable(sqliteContext.Vaults, pgContext.Vaults, pgContext, "Vaults"); + + // Identity framework related tables + await MigrateTable(sqliteContext.UserRoles, pgContext.UserRoles, pgContext, "UserRoles"); + await MigrateTable(sqliteContext.UserLogin, pgContext.UserLogin, pgContext, "UserLogins"); + await MigrateTable(sqliteContext.UserTokens, pgContext.UserTokens, pgContext, "UserTokens"); + + // Email related tables (last due to dependencies) + await MigrateTable(sqliteContext.Emails, pgContext.Emails, pgContext, "Emails"); + await MigrateTable(sqliteContext.EmailAttachments, pgContext.EmailAttachments, pgContext, "EmailAttachments"); + + Console.WriteLine("Migration completed successfully!"); + return 0; + } + catch (Exception ex) + { + Console.WriteLine($"Error during migration: {ex.Message}"); + Console.WriteLine(ex.InnerException); + return 1; + } + } + + /// + /// Migrates data from one database table to another, handling the transfer in batches. + /// + /// The entity type of the table being migrated. + /// The source database table. + /// The destination database table. + /// The destination database context. + /// The name of the table being migrated (for logging purposes). + /// A task representing the asynchronous migration operation. + /// + /// Thrown when the number of records in source and destination tables don't match after migration. + /// + /// + /// Thrown when a concurrency conflict occurs during the migration. + /// + private static async Task MigrateTable( + DbSet source, + DbSet destination, + DbContext destinationContext, + string tableName) + where T : class + { + Console.WriteLine($"Migrating {tableName}..."); + + var items = await source.ToListAsync(); + Console.WriteLine($"Found {items.Count} records to migrate"); + + if (items.Count > 0) + { + const int batchSize = 30; + foreach (var batch in items.Chunk(batchSize)) + { + try + { + await destination.AddRangeAsync(batch); + await destinationContext.SaveChangesAsync(); + Console.WriteLine($"Migrated {batch.Length} records from {tableName}"); + } + catch (DbUpdateConcurrencyException ex) + { + // Handle concurrency conflict + foreach (var entry in ex.Entries) + { + // Get current values from database + var databaseValues = await entry.GetDatabaseValuesAsync(); + + if (databaseValues == null) + { + // Row was deleted + entry.State = EntityState.Detached; + } + else + { + // Update the original values with the database values + entry.OriginalValues.SetValues(databaseValues); + + // Retry the save operation + await destinationContext.SaveChangesAsync(); + } + } + } + } + } + + // Ensure that the amount of records in the source and destination tables match + if (await source.CountAsync() != await destination.CountAsync()) + { + throw new ArgumentException($"The amount of records in the source and destination tables do not match. Check if the migration is working correctly."); + } + } +} From 14ac94b78a8984fb83500aa3729207f78885a555 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 13:08:48 +0100 Subject: [PATCH 31/62] Add migrate-db command to install.sh (#190) --- install.sh | 89 +++++++++++++++++++ .../AliasVault.InstallCli/Program.cs | 12 +-- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/install.sh b/install.sh index a11a3850..459479b9 100755 --- a/install.sh +++ b/install.sh @@ -51,6 +51,7 @@ show_usage() { printf " reset-password Reset admin password\n" printf " build Build AliasVault from source (takes longer and requires sufficient specs)\n" printf " configure-dev-db Configure development database (for local development only)\n" + printf " migrate-db Migrate data from SQLite to PostgreSQL\n" printf "\n" printf "Options:\n" @@ -136,6 +137,10 @@ parse_args() { shift fi ;; + migrate-db|migrate) + COMMAND="migrate-db" + shift + ;; --help) show_usage exit 0 @@ -223,6 +228,9 @@ main() { "configure-dev-db") configure_dev_database ;; + "migrate-db") + handle_migrate_db + ;; esac } @@ -1569,4 +1577,85 @@ print_dev_db_details() { printf "${MAGENTA}=========================================================${NC}\n" } +# Function to handle database migration +handle_migrate_db() { + printf "${YELLOW}+++ Database Migration Tool +++${NC}\n" + printf "\n" + + # Check for old SQLite database + SQLITE_DB="database/AliasServerDb.sqlite" + if [ ! -f "$SQLITE_DB" ]; then + printf "${RED}Error: SQLite database not found at ${SQLITE_DB}${NC}\n" + exit 1 + fi + + # Get the absolute path of the SQLite database + SQLITE_DB_ABS=$(realpath "$SQLITE_DB") + SQLITE_DB_DIR=$(dirname "$SQLITE_DB_ABS") + SQLITE_DB_NAME=$(basename "$SQLITE_DB_ABS") + + # Get PostgreSQL password from .env file + POSTGRES_PASSWORD=$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d '=' -f2) + if [ -z "$POSTGRES_PASSWORD" ]; then + printf "${RED}Error: POSTGRES_PASSWORD not found in .env file${NC}\n" + exit 1 + fi + + # Get network name in lowercase + NETWORK_NAME="$(pwd | xargs basename)_default" | tr '[:upper:]' '[:lower:]' + + printf "\n${YELLOW}Warning: This will migrate data from your SQLite database to PostgreSQL.${NC}\n" + printf "Source database: ${CYAN}${SQLITE_DB_ABS}${NC}\n" + printf "Target: PostgreSQL database (using connection string from docker-compose.yml)\n" + printf "Make sure you have backed up your data before proceeding.\n" + read -p "Continue with migration? [y/N]: " confirm + if [[ ! $confirm =~ ^[Yy]$ ]]; then + printf "${YELLOW}Migration cancelled.${NC}\n" + exit 0 + fi + + if ! docker pull ${GITHUB_CONTAINER_REGISTRY}-installcli:0.10.0 > /dev/null 2>&1; then + printf "${YELLOW}> Pre-built image not found, building locally...${NC}" + if [ "$VERBOSE" = true ]; then + docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile . + else + ( + docker build -t installcli -f src/Utilities/AliasVault.InstallCli/Dockerfile . > install_build_output.log 2>&1 & + BUILD_PID=$! + while kill -0 $BUILD_PID 2>/dev/null; do + printf "." + sleep 1 + done + printf "\n" + wait $BUILD_PID + BUILD_EXIT_CODE=$? + if [ $BUILD_EXIT_CODE -ne 0 ]; then + printf "\n${RED}> Error building Docker image. Check install_build_output.log for details.${NC}\n" + exit $BUILD_EXIT_CODE + fi + ) + fi + # Run migration with volume mount and connection string + HASH=$(docker run --rm \ + --network="${NETWORK_NAME}" \ + -v "${SQLITE_DB_DIR}:/sqlite" \ + -e "CONNECTION_STRING=Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" \ + ${GITHUB_CONTAINER_REGISTRY}-installcli:0.10.0 migrate-sqlite "/sqlite/${SQLITE_DB_NAME}") + else + # Run migration with volume mount using pre-built image + HASH=$(docker run --rm \ + --network="${NETWORK_NAME}" \ + -v "${SQLITE_DB_DIR}:/sqlite" \ + -e "CONNECTION_STRING=Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" \ + installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}") + fi + + if [ -z "$HASH" ]; then + printf "${RED}> Migration failed. Please check the error messages above.${NC}\n" + exit 1 + fi + + printf "${GREEN}> Migration completed successfully!${NC}\n" +} + main "$@" diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index 6cb72e00..069b9755 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -44,18 +44,18 @@ public static async Task Run(string[] args) return HashPassword(args[1]); case "migrate-sqlite": - if (args.Length != 2) + if (args.Length != 3) { - Console.WriteLine("Usage: migrate-sqlite "); + Console.WriteLine("Usage: migrate-sqlite "); return 1; } - return await MigrateSqliteToPostgres(args[1]); + return await MigrateSqliteToPostgres(args[1], args[2]); default: Console.WriteLine("Unknown command. Available commands:"); Console.WriteLine(" hash-password "); - Console.WriteLine(" migrate-sqlite "); + Console.WriteLine(" migrate-sqlite "); return 1; } } @@ -80,11 +80,12 @@ private static int HashPassword(string password) /// Migrates data from a SQLite database to a PostgreSQL database. /// /// The file path to the source SQLite database. + /// The connection string to the PostgreSQL database. /// /// Returns 0 if migration was successful, 1 if an error occurred. /// /// Thrown when a migration error occurs. - private static async Task MigrateSqliteToPostgres(string sqliteDbPath) + private static async Task MigrateSqliteToPostgres(string sqliteDbPath, string pgConnString) { try { @@ -98,7 +99,6 @@ private static async Task MigrateSqliteToPostgres(string sqliteDbPath) // Create connections to both databases var sqliteConnString = $"Data Source={sqliteDbPath}"; - var pgConnString = "Host=localhost;Port=5433;Database=aliasvault;Username=aliasvault;Password=password"; // Create contexts var optionsBuilderSqlite = new DbContextOptionsBuilder() From 4d43acb53f86c10be3d5bc78ede34daafb45ed48 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 13:36:58 +0100 Subject: [PATCH 32/62] Add build container start/stop/restart commands (#190) --- install.sh | 115 +++++++++++++----- .../AliasVault.InstallCli/Program.cs | 1 - 2 files changed, 87 insertions(+), 29 deletions(-) diff --git a/install.sh b/install.sh index 459479b9..801600db 100755 --- a/install.sh +++ b/install.sh @@ -45,11 +45,12 @@ show_usage() { printf " configure-ssl Configure SSL certificates (Let's Encrypt or self-signed)\n" printf " configure-email Configure email domains for receiving emails\n" printf " configure-registration Configure new account registration (enable or disable)\n" - printf " start Start AliasVault containers\n" - printf " stop Stop AliasVault containers\n" - printf " restart Restart AliasVault containers\n" + printf " start Start AliasVault containers using remote images\n" + printf " stop Stop AliasVault containers using remote images\n" + printf " restart Restart AliasVault containers using remote images\n" printf " reset-password Reset admin password\n" - printf " build Build AliasVault from source (takes longer and requires sufficient specs)\n" + printf " build [operation] Build AliasVault from source (takes longer and requires sufficient specs)\n" + printf " Optional operations: start|stop|restart (uses locally built images)\n" printf " configure-dev-db Configure development database (for local development only)\n" printf " migrate-db Migrate data from SQLite to PostgreSQL\n" @@ -58,6 +59,17 @@ show_usage() { printf " --verbose Show detailed output\n" printf " -y, --yes Automatic yes to prompts (for uninstall)\n" printf " --help Show this help message\n" + printf "\n" + printf "Examples:\n" + printf " $0 install Install AliasVault using remote images\n" + printf " $0 install start Install AliasVault using remote images and start containers\n" + printf " $0 install stop Stop containers using remote images\n" + printf " $0 install restart Restart containers using remote images\n" + printf " $0 build Build from source\n" + printf " $0 build start Start using local images\n" + printf " $0 build stop Stop containers using local build configuration\n" + printf " $0 build restart Restart containers using local build configuration\n" + } # Function to parse command line arguments @@ -83,10 +95,23 @@ parse_args() { shift fi ;; - # Other commands remain unchanged build|b) COMMAND="build" shift + # Check for additional operation argument + if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then + case $1 in + start|stop|restart) + COMMAND_ARG="$1" + shift + ;; + *) + echo "Invalid build operation: $1" + echo "Valid operations are: start, stop, restart" + exit 1 + ;; + esac + fi ;; uninstall|u) COMMAND="uninstall" @@ -184,19 +209,32 @@ main() { print_logo case $COMMAND in + "build") + if [ -z "$COMMAND_ARG" ]; then + handle_build + else + case $COMMAND_ARG in + "start") + handle_start "build" + ;; + "stop") + handle_stop "build" + ;; + "restart") + handle_restart "build" + ;; + esac + fi + ;; "install") handle_install "$COMMAND_ARG" ;; - "build") - handle_build - ;; "uninstall") handle_uninstall ;; "reset-password") generate_admin_password if [ $? -eq 0 ]; then - recreate_docker_containers print_password_reset_message fi ;; @@ -572,7 +610,12 @@ print_password_reset_message() { printf "\n" printf "${MAGENTA}=========================================================${NC}\n" printf "\n" - printf "${GREEN}The admin password is successfully reset, see the output above. You can now login to the admin panel using this new password.${NC}\n" + printf "${GREEN}The admin password has been successfully reset, see the output above.${NC}\n" + printf "\n" + printf "${YELLOW}Important: You must restart the admin container for the new password to take effect:${NC}\n" + printf " docker compose restart admin\n" + printf "\n" + printf "After restarting, you can login to the admin panel using the new password.\n" printf "\n" printf "${MAGENTA}=========================================================${NC}\n" printf "\n" @@ -1184,26 +1227,42 @@ generate_self_signed_cert() { # New functions to handle container lifecycle: handle_start() { + local mode="$1" printf "${CYAN}> Starting AliasVault containers...${NC}\n" - $(get_docker_compose_command) up -d + if [ "$mode" = "build" ]; then + $(get_docker_compose_command "build") up -d + else + $(get_docker_compose_command) up -d + fi printf "${GREEN}> AliasVault containers started successfully.${NC}\n" } handle_stop() { + local mode="$1" printf "${CYAN}> Stopping AliasVault containers...${NC}\n" if ! docker compose ps --quiet 2>/dev/null | grep -q .; then printf "${YELLOW}> No containers are currently running.${NC}\n" exit 0 fi - $(get_docker_compose_command) down + if [ "$mode" = "build" ]; then + $(get_docker_compose_command "build") down + else + $(get_docker_compose_command) down + fi printf "${GREEN}> AliasVault containers stopped successfully.${NC}\n" } handle_restart() { + local mode="$1" printf "${CYAN}> Restarting AliasVault containers...${NC}\n" - $(get_docker_compose_command) down - $(get_docker_compose_command) up -d + if [ "$mode" = "build" ]; then + $(get_docker_compose_command "build") down + $(get_docker_compose_command "build") up -d + else + $(get_docker_compose_command) down + $(get_docker_compose_command) up -d + fi printf "${GREEN}> AliasVault containers restarted successfully.${NC}\n" } @@ -1453,7 +1512,12 @@ handle_install_version() { # Start containers printf "\n${YELLOW}+++ Starting services +++${NC}\n" printf "\n" - recreate_docker_containers + if [ "$VERBOSE" = true ]; then + docker compose up -d --force-recreate + else + docker compose up -d --force-recreate > /dev/null 2>&1 + fi + printf "${GREEN}> Docker containers recreated.${NC}\n" # Only show success message if we made it here without errors print_success_message @@ -1602,7 +1666,8 @@ handle_migrate_db() { fi # Get network name in lowercase - NETWORK_NAME="$(pwd | xargs basename)_default" | tr '[:upper:]' '[:lower:]' + NETWORK_NAME="$(pwd | xargs basename)_default" + NETWORK_NAME=$(echo "$NETWORK_NAME" | tr '[:upper:]' '[:lower:]') printf "\n${YELLOW}Warning: This will migrate data from your SQLite database to PostgreSQL.${NC}\n" printf "Source database: ${CYAN}${SQLITE_DB_ABS}${NC}\n" @@ -1635,27 +1700,21 @@ handle_migrate_db() { fi ) fi + # Run migration with volume mount and connection string - HASH=$(docker run --rm \ + docker run --rm \ --network="${NETWORK_NAME}" \ -v "${SQLITE_DB_DIR}:/sqlite" \ - -e "CONNECTION_STRING=Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" \ - ${GITHUB_CONTAINER_REGISTRY}-installcli:0.10.0 migrate-sqlite "/sqlite/${SQLITE_DB_NAME}") + ${GITHUB_CONTAINER_REGISTRY}-installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" else # Run migration with volume mount using pre-built image - HASH=$(docker run --rm \ + docker run --rm \ --network="${NETWORK_NAME}" \ -v "${SQLITE_DB_DIR}:/sqlite" \ - -e "CONNECTION_STRING=Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" \ - installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}") - fi - - if [ -z "$HASH" ]; then - printf "${RED}> Migration failed. Please check the error messages above.${NC}\n" - exit 1 + installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" fi - printf "${GREEN}> Migration completed successfully!${NC}\n" + printf "${GREEN}> Check migration output above for details.${NC}\n" } main "$@" diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index 069b9755..af56080d 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -116,7 +116,6 @@ private static async Task MigrateSqliteToPostgres(string sqliteDbPath, stri // Make sure postgres is on latest version migration Console.WriteLine("Update postgres database to latest version..."); await using var pgContext = new AliasServerDbContextPostgresql(optionsBuilderPg.Options); - await pgContext.Database.EnsureDeletedAsync(); await pgContext.Database.MigrateAsync(); Console.WriteLine("Updating finished."); From a7502d42e47f251dd506b3ad155488369984df51 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 14:48:34 +0100 Subject: [PATCH 33/62] Fix migration tool params called from install.sh (#190) --- install.sh | 16 ++++-- .../AliasVault.InstallCli/Program.cs | 57 +++++++++++++++++-- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/install.sh b/install.sh index 801600db..ebb6d048 100755 --- a/install.sh +++ b/install.sh @@ -1659,7 +1659,7 @@ handle_migrate_db() { SQLITE_DB_NAME=$(basename "$SQLITE_DB_ABS") # Get PostgreSQL password from .env file - POSTGRES_PASSWORD=$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d '=' -f2) + POSTGRES_PASSWORD=$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d= -f2-) if [ -z "$POSTGRES_PASSWORD" ]; then printf "${RED}Error: POSTGRES_PASSWORD not found in .env file${NC}\n" exit 1 @@ -1670,9 +1670,17 @@ handle_migrate_db() { NETWORK_NAME=$(echo "$NETWORK_NAME" | tr '[:upper:]' '[:lower:]') printf "\n${YELLOW}Warning: This will migrate data from your SQLite database to PostgreSQL.${NC}\n" + printf "\n" + printf "This is a one-time operation necessary when upgrading from <= 0.9.x to 0.10.0+ and only needs to be run once.\n" + printf "\n" printf "Source database: ${CYAN}${SQLITE_DB_ABS}${NC}\n" printf "Target: PostgreSQL database (using connection string from docker-compose.yml)\n" printf "Make sure you have backed up your data before proceeding.\n" + + printf "\n${RED}WARNING: This operation will DELETE ALL EXISTING DATA in the PostgreSQL database.${NC}\n" + printf "${RED}Only proceed if you understand that any current PostgreSQL data will be permanently lost.${NC}\n" + printf "\n" + read -p "Continue with migration? [y/N]: " confirm if [[ ! $confirm =~ ^[Yy]$ ]]; then printf "${YELLOW}Migration cancelled.${NC}\n" @@ -1705,14 +1713,14 @@ handle_migrate_db() { docker run --rm \ --network="${NETWORK_NAME}" \ -v "${SQLITE_DB_DIR}:/sqlite" \ - ${GITHUB_CONTAINER_REGISTRY}-installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" + installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" else # Run migration with volume mount using pre-built image docker run --rm \ --network="${NETWORK_NAME}" \ -v "${SQLITE_DB_DIR}:/sqlite" \ - installcli migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" - fi + ${GITHUB_CONTAINER_REGISTRY}-installcli:0.10.0 migrate-sqlite "/sqlite/${SQLITE_DB_NAME}" "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" + fi printf "${GREEN}> Check migration output above for details.${NC}\n" } diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index af56080d..5d30a270 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -104,21 +104,40 @@ private static async Task MigrateSqliteToPostgres(string sqliteDbPath, stri var optionsBuilderSqlite = new DbContextOptionsBuilder() .UseSqlite(sqliteConnString); + var optionsBuilderPg = new DbContextOptionsBuilder() + .UseNpgsql(pgConnString); + // Make sure sqlite is on latest version migration Console.WriteLine("Update sqlite database to latest version..."); await using var sqliteContext = new AliasServerDbContextSqlite(optionsBuilderSqlite.Options); await sqliteContext.Database.MigrateAsync(); Console.WriteLine("Updating finished."); - var optionsBuilderPg = new DbContextOptionsBuilder() - .UseNpgsql(pgConnString); - // Make sure postgres is on latest version migration Console.WriteLine("Update postgres database to latest version..."); await using var pgContext = new AliasServerDbContextPostgresql(optionsBuilderPg.Options); await pgContext.Database.MigrateAsync(); Console.WriteLine("Updating finished."); + Console.WriteLine("Truncating existing tables in reverse dependency order..."); + + // Truncate tables in reverse order of dependencies + await TruncateTable(pgContext.EmailAttachments, "EmailAttachments"); + await TruncateTable(pgContext.Emails, "Emails"); + await TruncateTable(pgContext.UserTokens, "UserTokens"); + await TruncateTable(pgContext.UserRoles, "UserRoles"); + await TruncateTable(pgContext.UserLogin, "UserLogins"); + await TruncateTable(pgContext.UserEmailClaims, "UserEmailClaims"); + await TruncateTable(pgContext.Vaults, "Vaults"); + await TruncateTable(pgContext.UserEncryptionKeys, "UserEncryptionKeys"); + await TruncateTable(pgContext.AliasVaultUserRefreshTokens, "AliasVaultUserRefreshTokens"); + await TruncateTable(pgContext.AuthLogs, "AuthLogs"); + await TruncateTable(pgContext.DataProtectionKeys, "DataProtectionKeys"); + await TruncateTable(pgContext.ServerSettings, "ServerSettings"); + await TruncateTable(pgContext.AliasVaultUsers, "AliasVaultUsers"); + await TruncateTable(pgContext.AliasVaultRoles, "AliasVaultRoles"); + await TruncateTable(pgContext.AdminUsers, "AdminUsers"); + Console.WriteLine("Starting content migration..."); // First, migrate tables without foreign key dependencies @@ -155,6 +174,25 @@ private static async Task MigrateSqliteToPostgres(string sqliteDbPath, stri } } + /// + /// Truncates a table in the PostgreSQL database. + /// + /// The entity type of the table being truncated. + /// The database table to truncate. + /// The name of the table being truncated (for logging purposes). + /// A task representing the asynchronous truncation operation. + private static async Task TruncateTable(DbSet table, string tableName) + where T : class + { + Console.WriteLine($"Truncating table {tableName}..."); + var count = await table.CountAsync(); + if (count > 0) + { + await table.ExecuteDeleteAsync(); + Console.WriteLine($"Removed {count} records from {tableName}"); + } + } + /// /// Migrates data from one database table to another, handling the transfer in batches. /// @@ -184,6 +222,15 @@ private static async Task MigrateTable( if (items.Count > 0) { + // Remove any existing entries in the destination table + var existingEntries = await destination.ToListAsync(); + if (existingEntries.Any()) + { + Console.WriteLine($"Removing {existingEntries.Count} existing entries from {tableName}..."); + destination.RemoveRange(existingEntries); + await destinationContext.SaveChangesAsync(); + } + const int batchSize = 30; foreach (var batch in items.Chunk(batchSize)) { @@ -220,9 +267,9 @@ private static async Task MigrateTable( } // Ensure that the amount of records in the source and destination tables match - if (await source.CountAsync() != await destination.CountAsync()) + if (await source.CountAsync() > await destination.CountAsync()) { - throw new ArgumentException($"The amount of records in the source and destination tables do not match. Check if the migration is working correctly."); + throw new ArgumentException($"The amount of records in the source is greater than the destination. Check if the migration is working correctly."); } } } From 65553e0918e598944679cbb652ab6c9e772dd092 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 15:27:04 +0100 Subject: [PATCH 34/62] Update postgresql date column types to improve compatiblity (#190) --- .../AliasServerDbContextPostgresql.cs | 26 + ...20241224113431_LegacyTimestamp.Designer.cs | 908 ------------------ .../20241224113431_LegacyTimestamp.cs | 361 ------- ...241224140735_InitialMigration.Designer.cs} | 2 +- ....cs => 20241224140735_InitialMigration.cs} | 0 ...sServerDbContextPostgresqlModelSnapshot.cs | 42 +- .../PostgresqlDbContextFactory.cs | 2 + .../Handlers/DatabaseMessageStore.cs | 2 +- .../Abstracts/WebApplicationFactoryFixture.cs | 5 - .../WebApplicationApiFactoryFixture.cs | 5 + 10 files changed, 56 insertions(+), 1297 deletions(-) delete mode 100644 src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.Designer.cs delete mode 100644 src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.cs rename src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/{20241222185633_InitialMigration.Designer.cs => 20241224140735_InitialMigration.Designer.cs} (99%) rename src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/{20241222185633_InitialMigration.cs => 20241224140735_InitialMigration.cs} (100%) diff --git a/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs b/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs index ce4c8834..e17a9224 100644 --- a/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs +++ b/src/Databases/AliasServerDb/AliasServerDbContextPostgresql.cs @@ -8,6 +8,7 @@ namespace AliasServerDb; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.Extensions.Configuration; /// @@ -56,4 +57,29 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) .UseNpgsql(connectionString, options => options.CommandTimeout(60)) .UseLazyLoadingProxies(); } + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + // Configure all DateTime properties to use timestamp with time zone in UTC + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + foreach (var property in entityType.GetProperties()) + { + if (property.ClrType == typeof(DateTime) || property.ClrType == typeof(DateTime?)) + { + property.SetColumnType("timestamp with time zone"); + + // Add value converter for DateTime properties + var converter = new ValueConverter( + v => v.Kind == DateTimeKind.Utc ? v : v.ToUniversalTime(), + v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); + + property.SetValueConverter(converter); + } + } + } + } } diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.Designer.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.Designer.cs deleted file mode 100644 index 33d5c003..00000000 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.Designer.cs +++ /dev/null @@ -1,908 +0,0 @@ -// -using System; -using AliasServerDb; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace AliasServerDb.Migrations.PostgresqlMigrations -{ - [DbContext(typeof(AliasServerDbContextPostgresql))] - [Migration("20241224113431_LegacyTimestamp")] - partial class LegacyTimestamp - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Proxies:ChangeTracking", false) - .HasAnnotation("Proxies:CheckEquality", false) - .HasAnnotation("Proxies:LazyLoading", true) - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("AliasServerDb.AdminRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NormalizedName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("AdminRoles"); - }); - - modelBuilder.Entity("AliasServerDb.AdminUser", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .HasColumnType("text"); - - b.Property("Email") - .HasColumnType("text"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LastPasswordChanged") - .HasColumnType("timestamp without time zone"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasColumnType("text"); - - b.Property("NormalizedUserName") - .HasColumnType("text"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("AdminUsers"); - }); - - modelBuilder.Entity("AliasServerDb.AliasVaultRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NormalizedName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("AliasVaultRoles"); - }); - - modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("Blocked") - .HasColumnType("boolean"); - - b.Property("ConcurrencyStamp") - .HasColumnType("text"); - - b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("Email") - .HasColumnType("text"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasColumnType("text"); - - b.Property("NormalizedUserName") - .HasColumnType("text"); - - b.Property("PasswordChangedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("AliasVaultUsers"); - }); - - modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("DeviceIdentifier") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("ExpireDate") - .HasMaxLength(255) - .HasColumnType("timestamp without time zone"); - - b.Property("IpAddress") - .HasMaxLength(45) - .HasColumnType("character varying(45)"); - - b.Property("PreviousTokenValue") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Value") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AliasVaultUserRefreshTokens"); - }); - - modelBuilder.Entity("AliasServerDb.AuthLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AdditionalInfo") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Browser") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Country") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("DeviceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("EventType") - .HasColumnType("integer"); - - b.Property("FailureReason") - .HasColumnType("integer"); - - b.Property("IpAddress") - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("IsSuccess") - .HasColumnType("boolean"); - - b.Property("IsSuspiciousActivity") - .HasColumnType("boolean"); - - b.Property("OperatingSystem") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("RequestPath") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Timestamp") - .HasColumnType("timestamp without time zone"); - - b.Property("UserAgent") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("Username") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.HasIndex(new[] { "EventType" }, "IX_EventType"); - - b.HasIndex(new[] { "IpAddress" }, "IX_IpAddress"); - - b.HasIndex(new[] { "Timestamp" }, "IX_Timestamp"); - - b.HasIndex(new[] { "Username", "IsSuccess", "Timestamp" }, "IX_Username_IsSuccess_Timestamp") - .IsDescending(false, false, true); - - b.HasIndex(new[] { "Username", "Timestamp" }, "IX_Username_Timestamp") - .IsDescending(false, true); - - b.ToTable("AuthLogs"); - }); - - modelBuilder.Entity("AliasServerDb.Email", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Date") - .HasColumnType("timestamp without time zone"); - - b.Property("DateSystem") - .HasColumnType("timestamp without time zone"); - - b.Property("EncryptedSymmetricKey") - .IsRequired() - .HasColumnType("text"); - - b.Property("From") - .IsRequired() - .HasColumnType("text"); - - b.Property("FromDomain") - .IsRequired() - .HasColumnType("text"); - - b.Property("FromLocal") - .IsRequired() - .HasColumnType("text"); - - b.Property("MessageHtml") - .HasColumnType("text"); - - b.Property("MessagePlain") - .HasColumnType("text"); - - b.Property("MessagePreview") - .HasColumnType("text"); - - b.Property("MessageSource") - .IsRequired() - .HasColumnType("text"); - - b.Property("PushNotificationSent") - .HasColumnType("boolean"); - - b.Property("Subject") - .IsRequired() - .HasColumnType("text"); - - b.Property("To") - .IsRequired() - .HasColumnType("text"); - - b.Property("ToDomain") - .IsRequired() - .HasColumnType("text"); - - b.Property("ToLocal") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserEncryptionKeyId") - .HasMaxLength(255) - .HasColumnType("uuid"); - - b.Property("Visible") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("Date"); - - b.HasIndex("DateSystem"); - - b.HasIndex("PushNotificationSent"); - - b.HasIndex("ToLocal"); - - b.HasIndex("UserEncryptionKeyId"); - - b.HasIndex("Visible"); - - b.ToTable("Emails"); - }); - - modelBuilder.Entity("AliasServerDb.EmailAttachment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Bytes") - .IsRequired() - .HasColumnType("bytea"); - - b.Property("Date") - .HasColumnType("timestamp without time zone"); - - b.Property("EmailId") - .HasColumnType("integer"); - - b.Property("Filename") - .IsRequired() - .HasColumnType("text"); - - b.Property("Filesize") - .HasColumnType("integer"); - - b.Property("MimeType") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("EmailId"); - - b.ToTable("EmailAttachments"); - }); - - modelBuilder.Entity("AliasServerDb.Log", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Application") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Exception") - .IsRequired() - .HasColumnType("text"); - - b.Property("Level") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("LogEvent") - .IsRequired() - .HasColumnType("text") - .HasColumnName("LogEvent"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text"); - - b.Property("MessageTemplate") - .IsRequired() - .HasColumnType("text"); - - b.Property("Properties") - .IsRequired() - .HasColumnType("text"); - - b.Property("SourceContext") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("TimeStamp") - .HasColumnType("timestamp without time zone"); - - b.HasKey("Id"); - - b.HasIndex("Application"); - - b.HasIndex("TimeStamp"); - - b.ToTable("Logs", (string)null); - }); - - modelBuilder.Entity("AliasServerDb.ServerSetting", b => - { - b.Property("Key") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Key"); - - b.ToTable("ServerSettings"); - }); - - modelBuilder.Entity("AliasServerDb.TaskRunnerJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("EndTime") - .HasColumnType("time without time zone"); - - b.Property("ErrorMessage") - .HasColumnType("text"); - - b.Property("IsOnDemand") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("RunDate") - .HasColumnType("timestamp without time zone"); - - b.Property("StartTime") - .HasColumnType("time without time zone"); - - b.Property("Status") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("TaskRunnerJobs"); - }); - - modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Address") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("AddressDomain") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("AddressLocal") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.HasIndex("Address") - .IsUnique(); - - b.HasIndex("UserId"); - - b.ToTable("UserEmailClaims"); - }); - - modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("IsPrimary") - .HasColumnType("boolean"); - - b.Property("PublicKey") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UserEncryptionKeys"); - }); - - modelBuilder.Entity("AliasServerDb.Vault", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("CredentialsCount") - .HasColumnType("integer"); - - b.Property("EmailClaimsCount") - .HasColumnType("integer"); - - b.Property("EncryptionSettings") - .IsRequired() - .HasColumnType("text"); - - b.Property("EncryptionType") - .IsRequired() - .HasColumnType("text"); - - b.Property("FileSize") - .HasColumnType("integer"); - - b.Property("RevisionNumber") - .HasColumnType("bigint"); - - b.Property("Salt") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.Property("VaultBlob") - .IsRequired() - .HasColumnType("text"); - - b.Property("Verifier") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("Version") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Vaults"); - }); - - modelBuilder.Entity("AliasVault.WorkerStatus.Database.WorkerServiceStatus", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentStatus") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("DesiredStatus") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("Heartbeat") - .HasColumnType("timestamp without time zone"); - - b.Property("ServiceName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("WorkerServiceStatuses"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FriendlyName") - .HasColumnType("text"); - - b.Property("Xml") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("DataProtectionKeys"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("RoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("UserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.ToTable("UserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.ToTable("UserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("UserTokens", (string)null); - }); - - modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => - { - b.HasOne("AliasServerDb.AliasVaultUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("AliasServerDb.Email", b => - { - b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey") - .WithMany("Emails") - .HasForeignKey("UserEncryptionKeyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("EncryptionKey"); - }); - - modelBuilder.Entity("AliasServerDb.EmailAttachment", b => - { - b.HasOne("AliasServerDb.Email", "Email") - .WithMany("Attachments") - .HasForeignKey("EmailId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Email"); - }); - - modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => - { - b.HasOne("AliasServerDb.AliasVaultUser", "User") - .WithMany("EmailClaims") - .HasForeignKey("UserId"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => - { - b.HasOne("AliasServerDb.AliasVaultUser", "User") - .WithMany("EncryptionKeys") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("AliasServerDb.Vault", b => - { - b.HasOne("AliasServerDb.AliasVaultUser", "User") - .WithMany("Vaults") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => - { - b.Navigation("EmailClaims"); - - b.Navigation("EncryptionKeys"); - - b.Navigation("Vaults"); - }); - - modelBuilder.Entity("AliasServerDb.Email", b => - { - b.Navigation("Attachments"); - }); - - modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => - { - b.Navigation("Emails"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.cs deleted file mode 100644 index e887f16c..00000000 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224113431_LegacyTimestamp.cs +++ /dev/null @@ -1,361 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace AliasServerDb.Migrations.PostgresqlMigrations -{ - /// - public partial class LegacyTimestamp : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Heartbeat", - table: "WorkerServiceStatuses", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "Vaults", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "Vaults", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "UserEncryptionKeys", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "UserEncryptionKeys", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "UserEmailClaims", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "UserEmailClaims", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "RunDate", - table: "TaskRunnerJobs", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "ServerSettings", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "ServerSettings", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "TimeStamp", - table: "Logs", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "DateSystem", - table: "Emails", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "Date", - table: "Emails", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "Date", - table: "EmailAttachments", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "Timestamp", - table: "AuthLogs", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "AliasVaultUsers", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "PasswordChangedAt", - table: "AliasVaultUsers", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "AliasVaultUsers", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "ExpireDate", - table: "AliasVaultUserRefreshTokens", - type: "timestamp without time zone", - maxLength: 255, - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "AliasVaultUserRefreshTokens", - type: "timestamp without time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AlterColumn( - name: "LastPasswordChanged", - table: "AdminUsers", - type: "timestamp without time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone", - oldNullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Heartbeat", - table: "WorkerServiceStatuses", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "Vaults", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "Vaults", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "UserEncryptionKeys", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "UserEncryptionKeys", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "UserEmailClaims", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "UserEmailClaims", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "RunDate", - table: "TaskRunnerJobs", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "ServerSettings", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "ServerSettings", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "TimeStamp", - table: "Logs", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "DateSystem", - table: "Emails", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "Date", - table: "Emails", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "Date", - table: "EmailAttachments", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "Timestamp", - table: "AuthLogs", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "UpdatedAt", - table: "AliasVaultUsers", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "PasswordChangedAt", - table: "AliasVaultUsers", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "AliasVaultUsers", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "ExpireDate", - table: "AliasVaultUserRefreshTokens", - type: "timestamp with time zone", - maxLength: 255, - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone", - oldMaxLength: 255); - - migrationBuilder.AlterColumn( - name: "CreatedAt", - table: "AliasVaultUserRefreshTokens", - type: "timestamp with time zone", - nullable: false, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone"); - - migrationBuilder.AlterColumn( - name: "LastPasswordChanged", - table: "AdminUsers", - type: "timestamp with time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp without time zone", - oldNullable: true); - } - } -} diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.Designer.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.Designer.cs rename to src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.Designer.cs index a916fe20..661b2610 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.Designer.cs @@ -12,7 +12,7 @@ namespace AliasServerDb.Migrations.PostgresqlMigrations { [DbContext(typeof(AliasServerDbContextPostgresql))] - [Migration("20241222185633_InitialMigration")] + [Migration("20241224140735_InitialMigration")] partial class InitialMigration { /// diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.cs similarity index 100% rename from src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241222185633_InitialMigration.cs rename to src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.cs diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs index 5d3765cf..c0d63ab9 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs @@ -62,7 +62,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("boolean"); b.Property("LastPasswordChanged") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("LockoutEnabled") .HasColumnType("boolean"); @@ -133,7 +133,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text"); b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Email") .HasColumnType("text"); @@ -154,7 +154,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text"); b.Property("PasswordChangedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("PasswordHash") .HasColumnType("text"); @@ -172,7 +172,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("boolean"); b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("UserName") .HasColumnType("text"); @@ -189,7 +189,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid"); b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("DeviceIdentifier") .IsRequired() @@ -198,7 +198,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ExpireDate") .HasMaxLength(255) - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("IpAddress") .HasMaxLength(45) @@ -274,7 +274,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(100)"); b.Property("Timestamp") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("UserAgent") .HasMaxLength(255) @@ -311,10 +311,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Date") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("DateSystem") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("EncryptedSymmetricKey") .IsRequired() @@ -401,7 +401,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("bytea"); b.Property("Date") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("EmailId") .HasColumnType("integer"); @@ -469,7 +469,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(255)"); b.Property("TimeStamp") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -487,10 +487,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(255)"); b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Value") .HasColumnType("text"); @@ -523,7 +523,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(50)"); b.Property("RunDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("StartTime") .HasColumnType("time without time zone"); @@ -558,10 +558,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(255)"); b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("UserId") .HasMaxLength(255) @@ -584,7 +584,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid"); b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("IsPrimary") .HasColumnType("boolean"); @@ -595,7 +595,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(2000)"); b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("UserId") .IsRequired() @@ -616,7 +616,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid"); b.Property("CreatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("CredentialsCount") .HasColumnType("integer"); @@ -644,7 +644,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(100)"); b.Property("UpdatedAt") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("UserId") .IsRequired() @@ -691,7 +691,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("character varying(50)"); b.Property("Heartbeat") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ServiceName") .IsRequired() diff --git a/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs index 6170e0ce..0f883f9a 100644 --- a/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs +++ b/src/Databases/AliasServerDb/PostgresqlDbContextFactory.cs @@ -44,6 +44,8 @@ public Task CreateDbContextAsync(CancellationToken cancell /// public void ConfigureDbContextOptions(DbContextOptionsBuilder optionsBuilder) { + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + // Check environment variable first. var connectionString = Environment.GetEnvironmentVariable("ConnectionStrings__AliasServerDbContext"); diff --git a/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs b/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs index 106b5da8..d5f0cf50 100644 --- a/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs +++ b/src/Services/AliasVault.SmtpService/Handlers/DatabaseMessageStore.cs @@ -156,7 +156,7 @@ private static Email ConvertMimeMessageToEmail(MimeMessage message, MailAddress MessageHtml = message.HtmlBody, MessagePlain = message.TextBody, MessageSource = message.ToString(), - Date = message.Date.DateTime.ToUniversalTime(), + Date = message.Date.DateTime, DateSystem = DateTime.UtcNow, Visible = true, }; diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/Abstracts/WebApplicationFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/Abstracts/WebApplicationFactoryFixture.cs index bc14b20e..fa2a7e58 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/Abstracts/WebApplicationFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/Abstracts/WebApplicationFactoryFixture.cs @@ -43,11 +43,6 @@ public abstract class WebApplicationFactoryFixture : WebApplication /// public abstract int Port { get; set; } - /// - /// Gets the time provider instance for mutating the current time in tests. - /// - public TestTimeProvider TimeProvider { get; private set; } = new(); - /// /// Returns the DbContext instance for the test. This can be used to seed the database with test data. /// diff --git a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs index 6456b299..ef08ef31 100644 --- a/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs +++ b/src/Tests/AliasVault.E2ETests/Infrastructure/WebApplicationApiFactoryFixture.cs @@ -23,6 +23,11 @@ public class WebApplicationApiFactoryFixture : WebApplicationFactor /// public override int Port { get; set; } = 5001; + /// + /// Gets the time provider instance for mutating the current time in tests. + /// + public TestTimeProvider TimeProvider { get; private set; } = new(); + /// /// Removes existing service registrations. /// From 3b5e944417f7da9800f48570cc518d3b1dc8c154 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 15:48:34 +0100 Subject: [PATCH 35/62] Refactor (#190) --- src/AliasVault.Admin/Program.cs | 2 +- .../Configuration/DatabaseConfiguration.cs | 1 - .../SmtpServer/TestHostBuilder.cs | 2 + .../TaskRunner/TestHostBuilder.cs | 2 + .../AliasVault.InstallCli/Program.cs | 59 +++++++++---------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/AliasVault.Admin/Program.cs b/src/AliasVault.Admin/Program.cs index ae2abb8c..e1c5c4cf 100644 --- a/src/AliasVault.Admin/Program.cs +++ b/src/AliasVault.Admin/Program.cs @@ -133,7 +133,7 @@ using (var scope = app.Services.CreateScope()) { var container = scope.ServiceProvider; - await using var db = container.GetRequiredService().CreateDbContext(); + await using var db = await container.GetRequiredService().CreateDbContextAsync(); await db.Database.MigrateAsync(); await StartupTasks.CreateRolesIfNotExist(scope.ServiceProvider); diff --git a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs index 14262fc8..021b266e 100644 --- a/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs +++ b/src/Databases/AliasServerDb/Configuration/DatabaseConfiguration.cs @@ -52,7 +52,6 @@ public static IServiceCollection AddAliasVaultDatabaseConfiguration(this IServic case "postgresql": services.AddSingleton(); break; - case "sqlite": default: services.AddSingleton(); break; diff --git a/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs b/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs index 1a282f58..519482e7 100644 --- a/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs +++ b/src/Tests/AliasVault.IntegrationTests/SmtpServer/TestHostBuilder.cs @@ -184,6 +184,8 @@ FROM pg_stat_activity """; await cmd.ExecuteNonQueryAsync(); } + + GC.SuppressFinalize(this); } } } diff --git a/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs b/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs index a54325b6..71e10949 100644 --- a/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs +++ b/src/Tests/AliasVault.IntegrationTests/TaskRunner/TestHostBuilder.cs @@ -149,5 +149,7 @@ FROM pg_stat_activity await cmd.ExecuteNonQueryAsync(); } } + + GC.SuppressFinalize(this); } } diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index 5d30a270..ee34eb6b 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -10,12 +10,12 @@ using Microsoft.EntityFrameworkCore; // Add return type for top-level statements -return await Program.Run(args); +return await Run(args); /// /// Handles the migration of data between SQLite and PostgreSQL databases and password hashing utilities. /// -public partial class Program +public static partial class Program { /// /// Runs the program with the given arguments. @@ -222,15 +222,6 @@ private static async Task MigrateTable( if (items.Count > 0) { - // Remove any existing entries in the destination table - var existingEntries = await destination.ToListAsync(); - if (existingEntries.Any()) - { - Console.WriteLine($"Removing {existingEntries.Count} existing entries from {tableName}..."); - destination.RemoveRange(existingEntries); - await destinationContext.SaveChangesAsync(); - } - const int batchSize = 30; foreach (var batch in items.Chunk(batchSize)) { @@ -242,26 +233,7 @@ private static async Task MigrateTable( } catch (DbUpdateConcurrencyException ex) { - // Handle concurrency conflict - foreach (var entry in ex.Entries) - { - // Get current values from database - var databaseValues = await entry.GetDatabaseValuesAsync(); - - if (databaseValues == null) - { - // Row was deleted - entry.State = EntityState.Detached; - } - else - { - // Update the original values with the database values - entry.OriginalValues.SetValues(databaseValues); - - // Retry the save operation - await destinationContext.SaveChangesAsync(); - } - } + await HandleConcurrencyConflict(ex, destinationContext); } } } @@ -272,4 +244,29 @@ private static async Task MigrateTable( throw new ArgumentException($"The amount of records in the source is greater than the destination. Check if the migration is working correctly."); } } + + /// + /// Handles a concurrency conflict by updating the original values with the database values. + /// + /// The DbUpdateConcurrencyException that occurred. + /// The destination database context. + /// A task representing the asynchronous operation. + private static async Task HandleConcurrencyConflict( + DbUpdateConcurrencyException ex, + DbContext destinationContext) + { + foreach (var entry in ex.Entries) + { + var databaseValues = await entry.GetDatabaseValuesAsync(); + if (databaseValues == null) + { + entry.State = EntityState.Detached; + } + else + { + entry.OriginalValues.SetValues(databaseValues); + await destinationContext.SaveChangesAsync(); + } + } + } } From 141a291acea5f181e11158e6b7c18610b0d1ec09 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 20:49:38 +0100 Subject: [PATCH 36/62] Fix bug in db sync tests (#190) --- .../AliasVault.E2ETests/Tests/Client/Shard2/DbSyncTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard2/DbSyncTests.cs b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard2/DbSyncTests.cs index e2ae3431..18278542 100644 --- a/src/Tests/AliasVault.E2ETests/Tests/Client/Shard2/DbSyncTests.cs +++ b/src/Tests/AliasVault.E2ETests/Tests/Client/Shard2/DbSyncTests.cs @@ -245,6 +245,6 @@ await SimulateClient(baselineVault, async () => // Execute custom client actions. await clientActions(); - return await ApiDbContext.Vaults.OrderByDescending(x => x.UpdatedAt).FirstAsync(); + return await ApiDbContext.Vaults.OrderByDescending(x => x.RevisionNumber).FirstAsync(); } } From 80cc72eb221b4dddb43b4efa3ec9eb71e4d3d94a Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 21:53:07 +0100 Subject: [PATCH 37/62] Fix RecentEmails.razor dispose bug (#190) --- .../Main/Components/Email/RecentEmails.razor | 138 ++++++------------ 1 file changed, 47 insertions(+), 91 deletions(-) diff --git a/src/AliasVault.Client/Main/Components/Email/RecentEmails.razor b/src/AliasVault.Client/Main/Components/Email/RecentEmails.razor index a4abfbbf..05d7075a 100644 --- a/src/AliasVault.Client/Main/Components/Email/RecentEmails.razor +++ b/src/AliasVault.Client/Main/Components/Email/RecentEmails.razor @@ -11,7 +11,7 @@ @inject EmailService EmailService @using System.Timers @inject ILogger Logger -@implements IDisposable +@implements IAsyncDisposable @if (EmailModalVisible) { @@ -100,54 +100,74 @@ private bool EmailModalVisible { get; set; } private string Error { get; set; } = string.Empty; - private bool IsRefreshing { get; set; } = true; private bool IsLoading { get; set; } = true; private bool IsSpamOk { get; set; } = false; - private bool IsPageVisible { get; set; } = true; - private CancellationTokenSource? PollingCancellationTokenSource { get; set; } private const int ACTIVE_TAB_REFRESH_INTERVAL = 2000; // 2 seconds - private readonly SemaphoreSlim RefreshSemaphore = new(1, 1); - private DateTime LastRefreshTime = DateTime.MinValue; + + private PeriodicTimer? _refreshTimer; + private CancellationTokenSource _cancellationTokenSource = new(); /// - /// Callback invoked by JavaScript when the page visibility changes. + /// Callback invoked by JavaScript when the page visibility changes. This is used to start/stop the polling for new emails. /// - /// Boolean whether the page is visible or not. - /// Task. + /// Indicates whether the page is visible or not. [JSInvokable] public async Task OnVisibilityChange(bool isVisible) { - IsPageVisible = isVisible; - if (isVisible) + if (isVisible && DbService.Settings.AutoEmailRefresh) { - // Only enable auto-refresh if the setting is enabled. - if (DbService.Settings.AutoEmailRefresh) - { - await StartPolling(); - } + await StartPolling(); + } + else + { + await StopPolling(); + } - // Refresh immediately when tab becomes visible + // Refresh immediately when tab becomes visible + if (isVisible) + { await ManualRefresh(); } - else + } + + private async Task StartPolling() + { + await StopPolling(); + + // Create a new CancellationTokenSource since the old one might have been cancelled + _cancellationTokenSource = new CancellationTokenSource(); + _refreshTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(ACTIVE_TAB_REFRESH_INTERVAL)); + + try { - // Cancel polling. - if (PollingCancellationTokenSource is not null) + while (await _refreshTimer.WaitForNextTickAsync(_cancellationTokenSource.Token)) { - await PollingCancellationTokenSource.CancelAsync(); + await LoadRecentEmailsAsync(); } } - StateHasChanged(); + catch (OperationCanceledException) + { + // Normal cancellation, ignore + } + } + + private async Task StopPolling() + { + if (_refreshTimer is not null) + { + await _cancellationTokenSource.CancelAsync(); + _refreshTimer.Dispose(); + _refreshTimer = null; + } } /// - public void Dispose() + public async ValueTask DisposeAsync() { - PollingCancellationTokenSource?.Cancel(); - PollingCancellationTokenSource?.Dispose(); - RefreshSemaphore.Dispose(); + await StopPolling(); + _cancellationTokenSource.Dispose(); } /// @@ -161,16 +181,11 @@ } // Check if email has a known SpamOK domain, if not, don't show this component. - if (IsSpamOkDomain(EmailAddress) || IsAliasVaultDomain(EmailAddress)) - { - ShowComponent = true; - } + ShowComponent = IsSpamOkDomain(EmailAddress) || IsAliasVaultDomain(EmailAddress); IsSpamOk = IsSpamOkDomain(EmailAddress); - // Set up visibility change detection await JsInteropService.RegisterVisibilityCallback(DotNetObjectReference.Create(this)); - // Only enable auto-refresh if the setting is enabled. if (DbService.Settings.AutoEmailRefresh) { await StartPolling(); @@ -206,65 +221,6 @@ IsSpamOk = IsSpamOkDomain(EmailAddress); } - /// - /// Start the polling for new emails. - /// - /// Task. - private async Task StartPolling() - { - if (PollingCancellationTokenSource is not null) - { - await PollingCancellationTokenSource.CancelAsync(); - } - - PollingCancellationTokenSource = new CancellationTokenSource(); - - try - { - while (!PollingCancellationTokenSource.Token.IsCancellationRequested) - { - if (IsPageVisible) - { - // Only auto refresh when the tab is visible. - await RefreshWithThrottling(); - await Task.Delay(ACTIVE_TAB_REFRESH_INTERVAL, PollingCancellationTokenSource.Token); - } - } - } - catch (OperationCanceledException) - { - // Normal cancellation, ignore - } - } - - /// - /// Refresh the emails with throttling to prevent multiple refreshes at the same time. - /// - /// - private async Task RefreshWithThrottling() - { - if (!await RefreshSemaphore.WaitAsync(0)) // Don't wait if a refresh is in progress - { - return; - } - - try - { - var timeSinceLastRefresh = DateTime.UtcNow - LastRefreshTime; - if (timeSinceLastRefresh.TotalMilliseconds < ACTIVE_TAB_REFRESH_INTERVAL) - { - return; - } - - await LoadRecentEmailsAsync(); - LastRefreshTime = DateTime.UtcNow; - } - finally - { - RefreshSemaphore.Release(); - } - } - /// /// Returns true if the email address is from a known SpamOK domain. /// From 387267803967a5cb6d6387a54900ccd14dd26896 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 24 Dec 2024 22:20:44 +0100 Subject: [PATCH 38/62] Sanitize email when retrieving emails for emailbox (#190) --- src/AliasVault.Api/Controllers/Email/EmailBoxController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/AliasVault.Api/Controllers/Email/EmailBoxController.cs b/src/AliasVault.Api/Controllers/Email/EmailBoxController.cs index ac4cdddc..776d2278 100644 --- a/src/AliasVault.Api/Controllers/Email/EmailBoxController.cs +++ b/src/AliasVault.Api/Controllers/Email/EmailBoxController.cs @@ -41,9 +41,11 @@ public async Task GetEmailBox(string to) return Unauthorized("Not authenticated."); } + var sanitizedEmail = to.Trim().ToLower(); + // See if this user has a valid claim to the email address. var emailClaim = await context.UserEmailClaims - .FirstOrDefaultAsync(x => x.Address == to); + .FirstOrDefaultAsync(x => x.Address == sanitizedEmail); if (emailClaim is null) { @@ -51,7 +53,7 @@ public async Task GetEmailBox(string to) { Message = "No claim exists for this email address.", Code = "CLAIM_DOES_NOT_EXIST", - Details = new { ProvidedEmail = to }, + Details = new { ProvidedEmail = sanitizedEmail }, StatusCode = StatusCodes.Status400BadRequest, Timestamp = DateTime.UtcNow, }); From 26b1c4e04456ce35750514770b0e336a51e6ac41 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 12:11:17 +0100 Subject: [PATCH 39/62] Load security page components async (#190) --- .../Main/Pages/Settings/Security/Security.razor | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/AliasVault.Client/Main/Pages/Settings/Security/Security.razor b/src/AliasVault.Client/Main/Pages/Settings/Security/Security.razor index 3ac38669..0e7d8977 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/Security/Security.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/Security/Security.razor @@ -50,11 +50,11 @@ /// A task representing the asynchronous operation. private async Task LoadData() { - // Currently with SQLite, we have to load the data sequentially as otherwise we get database locks. - // When switched over to PostgreSQL, we can load the data concurrently. - await TwoFactorSection!.LoadData(); - await QuickVaultUnlockSection!.LoadData(); - await SessionsSection!.LoadData(); - await RecentAuthLogsSection!.LoadData(); + await Task.WhenAll( + TwoFactorSection!.LoadData(), + QuickVaultUnlockSection!.LoadData(), + SessionsSection!.LoadData(), + RecentAuthLogsSection!.LoadData() + ); } } From 577e02d7618d2231f4d6b68339948b075a9be85f Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 14:24:16 +0100 Subject: [PATCH 40/62] Update install script (#190) --- install.sh | 102 +++++++++++++++++++---------------------------------- 1 file changed, 36 insertions(+), 66 deletions(-) diff --git a/install.sh b/install.sh index ebb6d048..80fa6902 100755 --- a/install.sh +++ b/install.sh @@ -57,18 +57,9 @@ show_usage() { printf "\n" printf "Options:\n" printf " --verbose Show detailed output\n" - printf " -y, --yes Automatic yes to prompts (for uninstall)\n" + printf " -y, --yes Automatic yes to prompts\n" printf " --help Show this help message\n" printf "\n" - printf "Examples:\n" - printf " $0 install Install AliasVault using remote images\n" - printf " $0 install start Install AliasVault using remote images and start containers\n" - printf " $0 install stop Stop containers using remote images\n" - printf " $0 install restart Restart containers using remote images\n" - printf " $0 build Build from source\n" - printf " $0 build start Start using local images\n" - printf " $0 build stop Stop containers using local build configuration\n" - printf " $0 build restart Restart containers using local build configuration\n" } @@ -210,21 +201,7 @@ main() { print_logo case $COMMAND in "build") - if [ -z "$COMMAND_ARG" ]; then - handle_build - else - case $COMMAND_ARG in - "start") - handle_start "build" - ;; - "stop") - handle_stop "build" - ;; - "restart") - handle_restart "build" - ;; - esac - fi + handle_build ;; "install") handle_install "$COMMAND_ARG" @@ -235,6 +212,12 @@ main() { "reset-password") generate_admin_password if [ $? -eq 0 ]; then + printf "${CYAN}> Restarting admin container...${NC}\n" + if [ "$VERBOSE" = true ]; then + $(get_docker_compose_command) up -d --force-recreate admin + else + $(get_docker_compose_command) up -d --force-recreate admin > /dev/null 2>&1 + fi print_password_reset_message fi ;; @@ -596,13 +579,14 @@ print_success_message() { # Function to recreate (restart) Docker containers recreate_docker_containers() { - printf "${CYAN}> Recreating Docker containers...${NC}\n" + printf "${CYAN}> (Re)creating Docker containers...${NC}\n" + if [ "$VERBOSE" = true ]; then - docker compose up -d --force-recreate + $(get_docker_compose_command) up -d --force-recreate else - docker compose up -d --force-recreate > /dev/null 2>&1 + $(get_docker_compose_command) up -d --force-recreate > /dev/null 2>&1 fi - printf "${GREEN}> Docker containers recreated.${NC}\n" + printf "${GREEN}> Docker containers (re)created successfully.${NC}\n" } # Function to print password reset success message @@ -612,11 +596,6 @@ print_password_reset_message() { printf "\n" printf "${GREEN}The admin password has been successfully reset, see the output above.${NC}\n" printf "\n" - printf "${YELLOW}Important: You must restart the admin container for the new password to take effect:${NC}\n" - printf " docker compose restart admin\n" - printf "\n" - printf "After restarting, you can login to the admin panel using the new password.\n" - printf "\n" printf "${MAGENTA}=========================================================${NC}\n" printf "\n" } @@ -626,7 +605,7 @@ get_docker_compose_command() { local base_command="docker compose -f docker-compose.yml" # Check if using build configuration - if [ "$1" = "build" ]; then + if grep -q "^DEPLOYMENT_MODE=build" "$ENV_FILE" 2>/dev/null; then base_command="$base_command -f docker-compose.build.yml" fi @@ -749,6 +728,8 @@ handle_install() { # Function to handle build handle_build() { printf "${YELLOW}+++ Building AliasVault from source +++${NC}\n" + # Set deployment mode to build to ensure container lifecycle uses build configuration + set_deployment_mode "build" printf "\n" # Check for required build files @@ -811,17 +792,9 @@ handle_build() { printf "\n${GREEN}> Docker Compose stack built successfully.${NC}\n" printf "${CYAN}> Starting Docker Compose stack...${NC}\n" - if [ "$VERBOSE" = true ]; then - $(get_docker_compose_command "build") up -d --force-recreate || { - printf "${RED}> Failed to start Docker Compose stack${NC}\n" - exit 1 - } - else - $(get_docker_compose_command "build") up -d --force-recreate > /dev/null 2>&1 || { - printf "${RED}> Failed to start Docker Compose stack${NC}\n" - exit 1 - } - fi + + recreate_docker_containers + printf "${GREEN}> Docker Compose stack started successfully.${NC}\n" # Only show success message if we made it here without errors @@ -1227,42 +1200,26 @@ generate_self_signed_cert() { # New functions to handle container lifecycle: handle_start() { - local mode="$1" printf "${CYAN}> Starting AliasVault containers...${NC}\n" - if [ "$mode" = "build" ]; then - $(get_docker_compose_command "build") up -d - else - $(get_docker_compose_command) up -d - fi + $(get_docker_compose_command) up -d printf "${GREEN}> AliasVault containers started successfully.${NC}\n" } handle_stop() { - local mode="$1" printf "${CYAN}> Stopping AliasVault containers...${NC}\n" if ! docker compose ps --quiet 2>/dev/null | grep -q .; then printf "${YELLOW}> No containers are currently running.${NC}\n" exit 0 fi - if [ "$mode" = "build" ]; then - $(get_docker_compose_command "build") down - else - $(get_docker_compose_command) down - fi + $(get_docker_compose_command) down printf "${GREEN}> AliasVault containers stopped successfully.${NC}\n" } handle_restart() { - local mode="$1" printf "${CYAN}> Restarting AliasVault containers...${NC}\n" - if [ "$mode" = "build" ]; then - $(get_docker_compose_command "build") down - $(get_docker_compose_command "build") up -d - else - $(get_docker_compose_command) down - $(get_docker_compose_command) up -d - fi + $(get_docker_compose_command) down + $(get_docker_compose_command) up -d printf "${GREEN}> AliasVault containers restarted successfully.${NC}\n" } @@ -1457,6 +1414,8 @@ handle_install_version() { fi printf "${YELLOW}+++ Installing AliasVault ${target_version} +++${NC}\n" + # Set deployment mode to install to ensure container lifecycle uses install configuration + set_deployment_mode "install" printf "\n" # Initialize workspace which makes sure all required directories and files exist @@ -1465,6 +1424,7 @@ handle_install_version() { # Update docker-compose files with correct version so we pull the correct images handle_docker_compose "$target_version" + # Initialize environment create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; } populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; } @@ -1725,4 +1685,14 @@ handle_migrate_db() { printf "${GREEN}> Check migration output above for details.${NC}\n" } +# Function to set deployment mode in .env +set_deployment_mode() { + local mode=$1 + if [ "$mode" != "build" ] && [ "$mode" != "install" ]; then + printf "${RED}Invalid deployment mode: $mode${NC}\n" + exit 1 + fi + update_env_var "DEPLOYMENT_MODE" "$mode" +} + main "$@" From 82b2b7512782df8a9ce72f9e82f221d44b7564fa Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 14:24:31 +0100 Subject: [PATCH 41/62] Update docs with new update instructions (#190) --- docs/installation/advanced/manual-setup.md | 39 +++++++++++++----- .../{update.md => update/index.md} | 31 ++++++++++---- docs/installation/update/v0.10.0.md | 40 +++++++++++++++++++ 3 files changed, 93 insertions(+), 17 deletions(-) rename docs/installation/{update.md => update/index.md} (64%) create mode 100644 docs/installation/update/v0.10.0.md diff --git a/docs/installation/advanced/manual-setup.md b/docs/installation/advanced/manual-setup.md index a29168ab..80e294f4 100644 --- a/docs/installation/advanced/manual-setup.md +++ b/docs/installation/advanced/manual-setup.md @@ -20,7 +20,7 @@ If you prefer to manually set up AliasVault, this README provides step-by-step i Create the following directories in your project root: ```bash - mkdir -p certificates/ssl certificates/app database logs/msbuild + mkdir -p certificates/ssl certificates/app database/postgres ``` 2. **Create .env file** @@ -61,7 +61,21 @@ If you prefer to manually set up AliasVault, this README provides step-by-step i DATA_PROTECTION_CERT_PASS=your_generated_password_here ``` -6. **Set PRIVATE_EMAIL_DOMAINS** +6. **Configure PostgreSQL Settings** + + Set the following PostgreSQL-related variables in your .env file: + ```bash + # Database name (default: aliasvault) + POSTGRES_DB=aliasvault + + # Database user (default: aliasvault) + POSTGRES_USER=aliasvault + + # Generate a secure password for PostgreSQL + POSTGRES_PASSWORD=$(openssl rand -base64 32) + ``` + +7. **Set PRIVATE_EMAIL_DOMAINS** Update the .env file with allowed email domains. Use DISABLED.TLD to disable email support: ```bash @@ -72,14 +86,14 @@ If you prefer to manually set up AliasVault, this README provides step-by-step i PRIVATE_EMAIL_DOMAINS=DISABLED.TLD ``` -7. **Set SUPPORT_EMAIL (Optional)** +8. **Set SUPPORT_EMAIL (Optional)** Add a support email address if desired: ```bash SUPPORT_EMAIL=support@yourdomain.com ``` -8. **Generate admin password** +9. **Generate admin password** Build the Docker image for password hashing: ```bash @@ -97,7 +111,7 @@ If you prefer to manually set up AliasVault, this README provides step-by-step i ADMIN_PASSWORD_GENERATED=2024-01-01T00:00:00Z ``` -9. **Build and start Docker containers** +10. **Build and start Docker containers** Build the Docker Compose stack: ```bash @@ -109,22 +123,23 @@ If you prefer to manually set up AliasVault, this README provides step-by-step i docker compose up -d ``` -10. **Access AliasVault** +11. **Access AliasVault** AliasVault should now be running. You can access it at: - Admin Panel: https://localhost/admin - Username: admin - - Password: [Use the password you set in step 8] + - Password: [Use the password you set in step 9] - Client Website: https://localhost/ - Create your own account from here ## Important Notes -- Make sure to save the admin password you used in step 8 in a secure location. -- If you need to reset the admin password in the future, repeat step 8 and restart the Docker containers. +- Make sure to save both the admin password and PostgreSQL password in a secure location. +- If you need to reset the admin password in the future, repeat step 9 and restart the Docker containers. - Always keep your .env file secure and do not share it, as it contains sensitive information. +- The PostgreSQL data is persisted in the `database/postgres` directory. ## Troubleshooting @@ -134,7 +149,11 @@ If you encounter any issues during the setup: ```bash docker compose logs ``` -2. Ensure all required ports (80 and 443) are available and not being used by other services. +2. Ensure all required ports (80, 443, and 5432) are available and not being used by other services. 3. Verify that all environment variables in the .env file are set correctly. +4. Check PostgreSQL container logs specifically: + ```bash + docker compose logs postgres + ``` For further assistance, please refer to the project documentation or seek support through the appropriate channels. diff --git a/docs/installation/update.md b/docs/installation/update/index.md similarity index 64% rename from docs/installation/update.md rename to docs/installation/update/index.md index d4737bc1..e261771d 100644 --- a/docs/installation/update.md +++ b/docs/installation/update/index.md @@ -6,16 +6,26 @@ nav_order: 3 --- # Updating AliasVault -To update AliasVault to the latest version, run the install script with the `update` option. This will pull the latest version of AliasVault from GitHub and restart all containers. - +{: .no_toc } + +
+ + Table of contents + + {: .text-delta } +1. TOC +{:toc} +
+ +## Before You Begin You can see the latest available version of AliasVault on [GitHub](https://github.com/lanedirt/AliasVault/releases). {: .warning } Before updating, it's recommended to backup your database and other important data. You can do this by making a copy of the `database` and `certificates` directories. -## Updating to the latest available version -To update to the latest version, run the install script with the `update` option. The script will check for the latest version and prompt you to confirm the update. Follow the prompts to complete the update. +## Standard Update Process +For most version updates, you can use the standard update process: ```bash ./install.sh update @@ -23,7 +33,14 @@ To update to the latest version, run the install script with the `update` option > Tip: to skip the confirmation prompts and automatically proceed with the update, use the `-y` flag: `./install.sh update -y` -## Updating the installer script +## Version-Specific Upgrade Guides +Some versions require additional steps during upgrade. If you are upgrading from an older version, please check the relevant upgrade guide below: + +- [Updating to v0.10.0](v0-10-0.html) - SQLite to PostgreSQL migration + +## Additional Update Options + +### Updating the installer script The installer script can check for and apply updates to itself. This is done as part of the `update` command. However you can also update the installer script separately with the `update-installer` command. This is useful if you want to update the installer script without updating AliasVault itself, e.g. as a separate step during CI/CD pipeline. ```bash @@ -32,8 +49,8 @@ The installer script can check for and apply updates to itself. This is done as > Tip: to skip the confirmation prompts and automatically proceed with the update, use the `-y` flag: `./install.sh update-installer -y` -## Installing a specific version -To install a specific version and skip the automatic version checks, run the install script with the `install` option and specify the version you want to install. +### Installing a specific version +To install a specific version and skip the automatic version checks, run the install script with the `install` option and specify the version you want to install. Note that downgrading is not supported officially and may lead to unexpected issues. ```bash ./install.sh install diff --git a/docs/installation/update/v0.10.0.md b/docs/installation/update/v0.10.0.md new file mode 100644 index 00000000..8fbcbbe3 --- /dev/null +++ b/docs/installation/update/v0.10.0.md @@ -0,0 +1,40 @@ +--- +layout: default +title: Update to v0.10.0 +parent: Update +grand_parent: Installation Guide +nav_order: 1 +--- + +# Upgrading to v0.10.0 +{: .no_toc } + +This guide covers the upgrade process from version < v0.10.0 to v0.10.0 or newer, which includes a one-time database migration from SQLite to PostgreSQL. + +The v0.10.0 release introduces a new database backend, PostgreSQL, which replaces SQLite. This change is required because SQLite is not suitable for environments with concurrent writes that AliasVault requires. + +A built-in database migration tool is included in the installer script to help you migrate your data from SQLite to PostgreSQL. + +## Update Steps + +1. First, backup your existing SQLite database: +```bash +cp database/AliasServerDb.sqlite database/AliasServerDb.sqlite.backup +``` +2. Update AliasVault to the latest version: +```bash +./install.sh update +``` +3. Run the database migration tool: +```bash +./install.sh migrate-db +``` + +4. If the migration has completed successfully, restart AliasVault: +```bash +./install.sh restart +``` + +5. Test the upgrade by logging in to the admin panel and checking that your data is intact. + +If you encounter any issues during the upgrade, please create an issue on the [GitHub repository](https://github.com/lanedirt/AliasVault/issues) or contact via Discord. From 433664d85d50ed4117d15af3b75ed1c0a7b9b2a5 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 14:40:44 +0100 Subject: [PATCH 42/62] Fix admin redirect absolute URL bug (#190) --- src/AliasVault.Admin/Auth/Pages/Login.razor | 2 +- .../Main/Pages/Account/Manage/ResetAuthenticator.razor | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/AliasVault.Admin/Auth/Pages/Login.razor b/src/AliasVault.Admin/Auth/Pages/Login.razor index 00e99568..0e21b771 100644 --- a/src/AliasVault.Admin/Auth/Pages/Login.razor +++ b/src/AliasVault.Admin/Auth/Pages/Login.razor @@ -74,7 +74,7 @@ { await AuthLoggingService.LogAuthEventSuccessAsync(Input.UserName, AuthEventType.Login); Logger.LogInformation("User logged in."); - NavigationService.RedirectTo(ReturnUrl ?? "/"); + NavigationService.RedirectTo(ReturnUrl ?? "./"); } else if (result.RequiresTwoFactor) { diff --git a/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor b/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor index fda5231e..b70f714c 100644 --- a/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor +++ b/src/AliasVault.Admin/Main/Pages/Account/Manage/ResetAuthenticator.razor @@ -42,8 +42,7 @@ GlobalNotificationService.AddSuccessMessage("Your authenticator app key has been reset, you will need to re-configure your authenticator app using the new key."); - NavigationService.RedirectTo( - "account/manage/2fa"); + NavigationService.RedirectTo("account/manage/2fa"); } } From b5e575051c678b8f149636a1f2f8db2db797c1c2 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 16:44:15 +0100 Subject: [PATCH 43/62] Update migration logic to reset auto increment id (#190) --- .../AliasVault.InstallCli/Program.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index ee34eb6b..1b333ee9 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -131,6 +131,7 @@ private static async Task MigrateSqliteToPostgres(string sqliteDbPath, stri await TruncateTable(pgContext.Vaults, "Vaults"); await TruncateTable(pgContext.UserEncryptionKeys, "UserEncryptionKeys"); await TruncateTable(pgContext.AliasVaultUserRefreshTokens, "AliasVaultUserRefreshTokens"); + await TruncateTable(pgContext.Logs, "Logs"); await TruncateTable(pgContext.AuthLogs, "AuthLogs"); await TruncateTable(pgContext.DataProtectionKeys, "DataProtectionKeys"); await TruncateTable(pgContext.ServerSettings, "ServerSettings"); @@ -144,8 +145,9 @@ private static async Task MigrateSqliteToPostgres(string sqliteDbPath, stri await MigrateTable(sqliteContext.AliasVaultRoles, pgContext.AliasVaultRoles, pgContext, "AliasVaultRoles"); await MigrateTable(sqliteContext.AliasVaultUsers, pgContext.AliasVaultUsers, pgContext, "AliasVaultUsers"); await MigrateTable(sqliteContext.ServerSettings, pgContext.ServerSettings, pgContext, "ServerSettings"); - await MigrateTable(sqliteContext.DataProtectionKeys, pgContext.DataProtectionKeys, pgContext, "DataProtectionKeys"); - await MigrateTable(sqliteContext.AuthLogs, pgContext.AuthLogs, pgContext, "AuthLogs"); + await MigrateTable(sqliteContext.DataProtectionKeys, pgContext.DataProtectionKeys, pgContext, "DataProtectionKeys", true); + await MigrateTable(sqliteContext.Logs, pgContext.Logs, pgContext, "Logs", true); + await MigrateTable(sqliteContext.AuthLogs, pgContext.AuthLogs, pgContext, "AuthLogs", true); await MigrateTable(sqliteContext.AdminUsers, pgContext.AdminUsers, pgContext, "AdminUsers"); // Then migrate tables with foreign key dependencies @@ -160,8 +162,8 @@ private static async Task MigrateSqliteToPostgres(string sqliteDbPath, stri await MigrateTable(sqliteContext.UserTokens, pgContext.UserTokens, pgContext, "UserTokens"); // Email related tables (last due to dependencies) - await MigrateTable(sqliteContext.Emails, pgContext.Emails, pgContext, "Emails"); - await MigrateTable(sqliteContext.EmailAttachments, pgContext.EmailAttachments, pgContext, "EmailAttachments"); + await MigrateTable(sqliteContext.Emails, pgContext.Emails, pgContext, "Emails", true); + await MigrateTable(sqliteContext.EmailAttachments, pgContext.EmailAttachments, pgContext, "EmailAttachments", true); Console.WriteLine("Migration completed successfully!"); return 0; @@ -201,6 +203,7 @@ private static async Task TruncateTable(DbSet table, string tableName) /// The destination database table. /// The destination database context. /// The name of the table being migrated (for logging purposes). + /// Whether to reset the sequence for the table after migration. /// A task representing the asynchronous migration operation. /// /// Thrown when the number of records in source and destination tables don't match after migration. @@ -212,7 +215,8 @@ private static async Task MigrateTable( DbSet source, DbSet destination, DbContext destinationContext, - string tableName) + string tableName, + bool resetSequence = false) where T : class { Console.WriteLine($"Migrating {tableName}..."); @@ -236,6 +240,22 @@ private static async Task MigrateTable( await HandleConcurrencyConflict(ex, destinationContext); } } + + // Only reset sequence if requested + if (resetSequence && destinationContext.Database.ProviderName == "Npgsql.EntityFrameworkCore.PostgreSQL") + { + var tablePgName = destinationContext.Model.FindEntityType(typeof(T))?.GetTableName(); + if (!string.IsNullOrEmpty(tablePgName)) + { + var schema = destinationContext.Model.FindEntityType(typeof(T))?.GetSchema() ?? "public"; + var sql = $""" + SELECT setval(pg_get_serial_sequence('{schema}."{tablePgName}"', 'Id'), + (SELECT COALESCE(MAX("Id"::integer), 0) + 1 FROM {schema}."{tablePgName}"), false); + """; + await destinationContext.Database.ExecuteSqlRawAsync(sql); + Console.WriteLine($"Reset sequence for {tableName}"); + } + } } // Ensure that the amount of records in the source and destination tables match From 59599f43a34bb003e823af38fb23c82610ba7180 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 16:53:30 +0100 Subject: [PATCH 44/62] Update docs (#190) --- docs/installation/update/v0.10.0.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/installation/update/v0.10.0.md b/docs/installation/update/v0.10.0.md index 8fbcbbe3..2c009745 100644 --- a/docs/installation/update/v0.10.0.md +++ b/docs/installation/update/v0.10.0.md @@ -29,8 +29,7 @@ cp database/AliasServerDb.sqlite database/AliasServerDb.sqlite.backup ```bash ./install.sh migrate-db ``` - -4. If the migration has completed successfully, restart AliasVault: +4. After the migration has completed successfully, restart all AliasVault containers: ```bash ./install.sh restart ``` From f959b7dc9193fe47481bee950adbf0297b74cd2c Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 22:21:13 +0100 Subject: [PATCH 45/62] Update install.sh (#190) --- install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/install.sh b/install.sh index 80fa6902..77f6a040 100755 --- a/install.sh +++ b/install.sh @@ -16,6 +16,7 @@ REQUIRED_DIRS=( "certificates/letsencrypt" "certificates/letsencrypt/www" "database" + "database/postgres" "logs" "logs/msbuild" ) From 96997c7d8d29b6b38a405528e6c7e1ca60394648 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 22:21:19 +0100 Subject: [PATCH 46/62] Update docs (#190) --- docs/installation/advanced/build-from-source.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/advanced/build-from-source.md b/docs/installation/advanced/build-from-source.md index f55e4944..b2384ebe 100644 --- a/docs/installation/advanced/build-from-source.md +++ b/docs/installation/advanced/build-from-source.md @@ -9,7 +9,7 @@ nav_order: 1 Instead of using the pre-built Docker images, you can also build the images from source yourself. This allows you to build a specific version of AliasVault and/or to make changes to the source code. Building from source requires more resources: -- Minimum 2GB RAM (more RAM will speed up build time) +- Minimum 4GB RAM (more RAM will speed up build time) - At least 1 vCPU - 40GB+ disk space (for dependencies and build artifacts) - Docker installed From 4f5e8227226f9999066d5ae509b1c2ab1792eb94 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 22:23:43 +0100 Subject: [PATCH 47/62] Include postgres empty dir in git to ensure correct permissions (#190) --- .gitignore | 1 - database/postgres/README.md | 1 + install.sh | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 database/postgres/README.md diff --git a/.gitignore b/.gitignore index 46bbb308..27f46504 100644 --- a/.gitignore +++ b/.gitignore @@ -413,5 +413,4 @@ docs/vendor docs/.bundle # Database files -database/postgres database/postgres-dev diff --git a/database/postgres/README.md b/database/postgres/README.md new file mode 100644 index 00000000..68abfb8f --- /dev/null +++ b/database/postgres/README.md @@ -0,0 +1 @@ +This is the default location where the postgres database files are stored. \ No newline at end of file diff --git a/install.sh b/install.sh index 77f6a040..d294c729 100755 --- a/install.sh +++ b/install.sh @@ -1425,7 +1425,6 @@ handle_install_version() { # Update docker-compose files with correct version so we pull the correct images handle_docker_compose "$target_version" - # Initialize environment create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; } populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; } From cb330219abe49f5adbc2d746929cee16a4d7cae0 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 22:26:58 +0100 Subject: [PATCH 48/62] Refactor postgres db folder creation (#190) --- .gitignore | 1 + database/postgres/README.md | 1 - install.sh | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 database/postgres/README.md diff --git a/.gitignore b/.gitignore index 27f46504..46bbb308 100644 --- a/.gitignore +++ b/.gitignore @@ -413,4 +413,5 @@ docs/vendor docs/.bundle # Database files +database/postgres database/postgres-dev diff --git a/database/postgres/README.md b/database/postgres/README.md deleted file mode 100644 index 68abfb8f..00000000 --- a/database/postgres/README.md +++ /dev/null @@ -1 +0,0 @@ -This is the default location where the postgres database files are stored. \ No newline at end of file diff --git a/install.sh b/install.sh index d294c729..8893875c 100755 --- a/install.sh +++ b/install.sh @@ -733,6 +733,9 @@ handle_build() { set_deployment_mode "build" printf "\n" + # Initialize workspace which makes sure all required directories and files exist + initialize_workspace + # Check for required build files if [ ! -f "docker-compose.build.yml" ] || [ ! -d "src" ]; then printf "${RED}Error: Required files for building from source are missing.${NC}\n" From bf40539e9245077abf1483489d1a7be94d2bf000 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 22:41:40 +0100 Subject: [PATCH 49/62] Update dockerignore to ignore data directories during build (#190) --- .dockerignore | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index cd967fc3..3c53eedb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -22,4 +22,17 @@ **/secrets.dev.yaml **/values.dev.yaml LICENSE -README.md \ No newline at end of file +README.md + +# Exclude AliasVault data directories +database/ +logs/ +certificates/ + +# Exclude git directory +.git/ + +# Exclude development files +*.log +*.env +*.env.* \ No newline at end of file From a75d5c7a34c044f83daffa3298e402cd4b92cbca Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 22:41:56 +0100 Subject: [PATCH 50/62] Update migration add data truncation if source data exceeds length (#190) --- .../AliasVault.InstallCli/Program.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index 1b333ee9..576e46af 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -226,7 +226,34 @@ private static async Task MigrateTable( if (items.Count > 0) { - const int batchSize = 30; + // Get entity type from the model to check annotations + var entityType = destinationContext.Model.FindEntityType(typeof(T)); + + foreach (var item in items) + { + // Check each property for MaxLength annotation + foreach (var property in entityType!.GetProperties()) + { + // Only process string properties + if (property.ClrType == typeof(string)) + { + var maxLength = property.GetMaxLength(); + if (maxLength.HasValue) + { + var propertyInfo = typeof(T).GetProperty(property.Name); + var value = propertyInfo?.GetValue(item) as string; + + if (value?.Length > maxLength.Value) + { + propertyInfo?.SetValue(item, value.Substring(0, maxLength.Value)); + Console.WriteLine($"Truncated {property.Name} in {tableName} from {value.Length} to {maxLength.Value} characters"); + } + } + } + } + } + + const int batchSize = 50; foreach (var batch in items.Chunk(batchSize)) { try From 254104e12d808f1648b40605f52fd87cf21f962a Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 22:46:08 +0100 Subject: [PATCH 51/62] Update migration logging (#190) --- src/Utilities/AliasVault.InstallCli/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index 576e46af..6abff8ff 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -264,6 +264,7 @@ private static async Task MigrateTable( } catch (DbUpdateConcurrencyException ex) { + Console.WriteLine($"Concurrency conflict occurred during migration of {tableName}..."); await HandleConcurrencyConflict(ex, destinationContext); } } @@ -315,5 +316,7 @@ private static async Task HandleConcurrencyConflict( await destinationContext.SaveChangesAsync(); } } + + Console.WriteLine($"Concurrency conflict resolved, {ex.Entries.Count} records inserted"); } } From 2631a1f0b143498f5f66700fdb84870780c948a6 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 22:47:44 +0100 Subject: [PATCH 52/62] Update migration (#190) --- src/Utilities/AliasVault.InstallCli/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index 6abff8ff..62560b69 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -266,6 +266,7 @@ private static async Task MigrateTable( { Console.WriteLine($"Concurrency conflict occurred during migration of {tableName}..."); await HandleConcurrencyConflict(ex, destinationContext); + Console.WriteLine($"Concurrency conflict resolved, {batch.Length} records inserted"); } } @@ -316,7 +317,5 @@ private static async Task HandleConcurrencyConflict( await destinationContext.SaveChangesAsync(); } } - - Console.WriteLine($"Concurrency conflict resolved, {ex.Entries.Count} records inserted"); } } From f0e0e9c03e330f8487fc47b097642d52799e7eb8 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 22:55:10 +0100 Subject: [PATCH 53/62] Add TaskRunnerJobs to migration (#190) --- src/Utilities/AliasVault.InstallCli/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index 62560b69..ddd9efee 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -135,6 +135,7 @@ private static async Task MigrateSqliteToPostgres(string sqliteDbPath, stri await TruncateTable(pgContext.AuthLogs, "AuthLogs"); await TruncateTable(pgContext.DataProtectionKeys, "DataProtectionKeys"); await TruncateTable(pgContext.ServerSettings, "ServerSettings"); + await TruncateTable(pgContext.TaskRunnerJobs, "TaskRunnerJobs"); await TruncateTable(pgContext.AliasVaultUsers, "AliasVaultUsers"); await TruncateTable(pgContext.AliasVaultRoles, "AliasVaultRoles"); await TruncateTable(pgContext.AdminUsers, "AdminUsers"); @@ -145,6 +146,7 @@ private static async Task MigrateSqliteToPostgres(string sqliteDbPath, stri await MigrateTable(sqliteContext.AliasVaultRoles, pgContext.AliasVaultRoles, pgContext, "AliasVaultRoles"); await MigrateTable(sqliteContext.AliasVaultUsers, pgContext.AliasVaultUsers, pgContext, "AliasVaultUsers"); await MigrateTable(sqliteContext.ServerSettings, pgContext.ServerSettings, pgContext, "ServerSettings"); + await MigrateTable(sqliteContext.TaskRunnerJobs, pgContext.TaskRunnerJobs, pgContext, "TaskRunnerJobs"); await MigrateTable(sqliteContext.DataProtectionKeys, pgContext.DataProtectionKeys, pgContext, "DataProtectionKeys", true); await MigrateTable(sqliteContext.Logs, pgContext.Logs, pgContext, "Logs", true); await MigrateTable(sqliteContext.AuthLogs, pgContext.AuthLogs, pgContext, "AuthLogs", true); From fc85f34218ca0df16688f7f6d6c17635acc82b08 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 23:01:54 +0100 Subject: [PATCH 54/62] Update useremailclaims setnull to be compatible with PostgreSQL (#190) --- src/Databases/AliasServerDb/AliasServerDbContext.cs | 8 ++++---- ...ner.cs => 20241225220047_InitialMigration.Designer.cs} | 5 +++-- ...ialMigration.cs => 20241225220047_InitialMigration.cs} | 3 ++- .../AliasServerDbContextPostgresqlModelSnapshot.cs | 3 ++- 4 files changed, 11 insertions(+), 8 deletions(-) rename src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/{20241224140735_InitialMigration.Designer.cs => 20241225220047_InitialMigration.Designer.cs} (99%) rename src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/{20241224140735_InitialMigration.cs => 20241225220047_InitialMigration.cs} (99%) diff --git a/src/Databases/AliasServerDb/AliasServerDbContext.cs b/src/Databases/AliasServerDb/AliasServerDbContext.cs index ae1468bd..6b911f76 100644 --- a/src/Databases/AliasServerDb/AliasServerDbContext.cs +++ b/src/Databases/AliasServerDb/AliasServerDbContext.cs @@ -221,10 +221,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // Note: when a user is deleted the email claims user FK's should be set to NULL // so the claims themselves are preserved to prevent re-use of the email address. modelBuilder.Entity() - .HasOne(l => l.User) - .WithMany(c => c.EmailClaims) - .HasForeignKey(l => l.UserId) - .OnDelete(DeleteBehavior.ClientSetNull); + .HasOne(e => e.User) + .WithMany(u => u.EmailClaims) + .HasForeignKey(e => e.UserId) + .OnDelete(DeleteBehavior.SetNull); // Configure Email - UserEncryptionKey relationship modelBuilder.Entity() diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.Designer.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241225220047_InitialMigration.Designer.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.Designer.cs rename to src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241225220047_InitialMigration.Designer.cs index 661b2610..d53285c3 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.Designer.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241225220047_InitialMigration.Designer.cs @@ -12,7 +12,7 @@ namespace AliasServerDb.Migrations.PostgresqlMigrations { [DbContext(typeof(AliasServerDbContextPostgresql))] - [Migration("20241224140735_InitialMigration")] + [Migration("20241225220047_InitialMigration")] partial class InitialMigration { /// @@ -857,7 +857,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { b.HasOne("AliasServerDb.AliasVaultUser", "User") .WithMany("EmailClaims") - .HasForeignKey("UserId"); + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); b.Navigation("User"); }); diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241225220047_InitialMigration.cs similarity index 99% rename from src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.cs rename to src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241225220047_InitialMigration.cs index 199fa00c..f796a019 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241224140735_InitialMigration.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/20241225220047_InitialMigration.cs @@ -319,7 +319,8 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "FK_UserEmailClaims_AliasVaultUsers_UserId", column: x => x.UserId, principalTable: "AliasVaultUsers", - principalColumn: "Id"); + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); }); migrationBuilder.CreateTable( diff --git a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs index c0d63ab9..aec0c01e 100644 --- a/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/PostgresqlMigrations/AliasServerDbContextPostgresqlModelSnapshot.cs @@ -854,7 +854,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("AliasServerDb.AliasVaultUser", "User") .WithMany("EmailClaims") - .HasForeignKey("UserId"); + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); b.Navigation("User"); }); From 747e0910cb56f63f02294976265dbd28e8dd5ce9 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 25 Dec 2024 23:10:24 +0100 Subject: [PATCH 55/62] Update SQLite ef model (#190) --- .../20241223201909_IncludePostgresqlFixes.cs | 3 +- ...UpdateAliasVaultEmailClaimNull.Designer.cs | 887 ++++++++++++++++++ ...25220908_UpdateAliasVaultEmailClaimNull.cs | 42 + ...AliasServerDbContextSqliteModelSnapshot.cs | 3 +- 4 files changed, 933 insertions(+), 2 deletions(-) create mode 100644 src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241225220908_UpdateAliasVaultEmailClaimNull.Designer.cs create mode 100644 src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241225220908_UpdateAliasVaultEmailClaimNull.cs diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.cs index f86f0d31..ce0d0fd0 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241223201909_IncludePostgresqlFixes.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241225220908_UpdateAliasVaultEmailClaimNull.Designer.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241225220908_UpdateAliasVaultEmailClaimNull.Designer.cs new file mode 100644 index 00000000..6c71a53a --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241225220908_UpdateAliasVaultEmailClaimNull.Designer.cs @@ -0,0 +1,887 @@ +// +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.SqliteMigrations +{ + [DbContext(typeof(AliasServerDbContextSqlite))] + [Migration("20241225220908_UpdateAliasVaultEmailClaimNull")] + partial class UpdateAliasVaultEmailClaimNull + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true); + + modelBuilder.Entity("AliasServerDb.AdminRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AdminRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AdminUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LastPasswordChanged") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AdminUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultRoles"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Blocked") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT"); + + b.Property("PasswordChangedAt") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("AliasVaultUsers"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeviceIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("ExpireDate") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(45) + .HasColumnType("TEXT"); + + b.Property("PreviousTokenValue") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AliasVaultUserRefreshTokens"); + }); + + modelBuilder.Entity("AliasServerDb.AuthLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalInfo") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Browser") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("FailureReason") + .HasColumnType("INTEGER"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("IsSuccess") + .HasColumnType("INTEGER"); + + b.Property("IsSuspiciousActivity") + .HasColumnType("INTEGER"); + + b.Property("OperatingSystem") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("RequestPath") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.Property("UserAgent") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EventType" }, "IX_EventType"); + + b.HasIndex(new[] { "IpAddress" }, "IX_IpAddress"); + + b.HasIndex(new[] { "Timestamp" }, "IX_Timestamp"); + + b.HasIndex(new[] { "Username", "IsSuccess", "Timestamp" }, "IX_Username_IsSuccess_Timestamp") + .IsDescending(false, false, true); + + b.HasIndex(new[] { "Username", "Timestamp" }, "IX_Username_Timestamp") + .IsDescending(false, true); + + b.ToTable("AuthLogs"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DateSystem") + .HasColumnType("TEXT"); + + b.Property("EncryptedSymmetricKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("From") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FromDomain") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FromLocal") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MessageHtml") + .HasColumnType("TEXT"); + + b.Property("MessagePlain") + .HasColumnType("TEXT"); + + b.Property("MessagePreview") + .HasColumnType("TEXT"); + + b.Property("MessageSource") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PushNotificationSent") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("To") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToDomain") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToLocal") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserEncryptionKeyId") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("DateSystem"); + + b.HasIndex("PushNotificationSent"); + + b.HasIndex("ToLocal"); + + b.HasIndex("UserEncryptionKeyId"); + + b.HasIndex("Visible"); + + b.ToTable("Emails"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bytes") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("EmailId") + .HasColumnType("INTEGER"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Filesize") + .HasColumnType("INTEGER"); + + b.Property("MimeType") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EmailId"); + + b.ToTable("EmailAttachments"); + }); + + modelBuilder.Entity("AliasServerDb.Log", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Application") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Exception") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("LogEvent") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("LogEvent"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MessageTemplate") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Properties") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SourceContext") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("TimeStamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Application"); + + b.HasIndex("TimeStamp"); + + b.ToTable("Logs", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.ServerSetting", b => + { + b.Property("Key") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("AliasServerDb.TaskRunnerJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EndTime") + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT"); + + b.Property("IsOnDemand") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RunDate") + .HasColumnType("TEXT"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TaskRunnerJobs"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AddressDomain") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("AddressLocal") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Address") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("UserEmailClaims"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsPrimary") + .HasColumnType("INTEGER"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserEncryptionKeys"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CredentialsCount") + .HasColumnType("INTEGER"); + + b.Property("EmailClaimsCount") + .HasColumnType("INTEGER"); + + b.Property("EncryptionSettings") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EncryptionType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FileSize") + .HasColumnType("INTEGER"); + + b.Property("RevisionNumber") + .HasColumnType("INTEGER"); + + b.Property("Salt") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("VaultBlob") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Verifier") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("AliasVault.WorkerStatus.Database.WorkerServiceStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CurrentStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DesiredStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Heartbeat") + .HasColumnType("TEXT"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WorkerServiceStatuses"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FriendlyName") + .HasColumnType("TEXT"); + + b.Property("Xml") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("UserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", (string)null); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUserRefreshToken", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.HasOne("AliasServerDb.UserEncryptionKey", "EncryptionKey") + .WithMany("Emails") + .HasForeignKey("UserEncryptionKeyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EncryptionKey"); + }); + + modelBuilder.Entity("AliasServerDb.EmailAttachment", b => + { + b.HasOne("AliasServerDb.Email", "Email") + .WithMany("Attachments") + .HasForeignKey("EmailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Email"); + }); + + modelBuilder.Entity("AliasServerDb.UserEmailClaim", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EmailClaims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("EncryptionKeys") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.Vault", b => + { + b.HasOne("AliasServerDb.AliasVaultUser", "User") + .WithMany("Vaults") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("AliasServerDb.AliasVaultUser", b => + { + b.Navigation("EmailClaims"); + + b.Navigation("EncryptionKeys"); + + b.Navigation("Vaults"); + }); + + modelBuilder.Entity("AliasServerDb.Email", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("AliasServerDb.UserEncryptionKey", b => + { + b.Navigation("Emails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241225220908_UpdateAliasVaultEmailClaimNull.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241225220908_UpdateAliasVaultEmailClaimNull.cs new file mode 100644 index 00000000..857e2b11 --- /dev/null +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/20241225220908_UpdateAliasVaultEmailClaimNull.cs @@ -0,0 +1,42 @@ +// +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AliasServerDb.Migrations.SqliteMigrations +{ + /// + public partial class UpdateAliasVaultEmailClaimNull : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_UserEmailClaims_AliasVaultUsers_UserId", + table: "UserEmailClaims"); + + migrationBuilder.AddForeignKey( + name: "FK_UserEmailClaims_AliasVaultUsers_UserId", + table: "UserEmailClaims", + column: "UserId", + principalTable: "AliasVaultUsers", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_UserEmailClaims_AliasVaultUsers_UserId", + table: "UserEmailClaims"); + + migrationBuilder.AddForeignKey( + name: "FK_UserEmailClaims_AliasVaultUsers_UserId", + table: "UserEmailClaims", + column: "UserId", + principalTable: "AliasVaultUsers", + principalColumn: "Id"); + } + } +} diff --git a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs index 6d113093..744c72ae 100644 --- a/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs +++ b/src/Databases/AliasServerDb/Migrations/SqliteMigrations/AliasServerDbContextSqliteModelSnapshot.cs @@ -832,7 +832,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.HasOne("AliasServerDb.AliasVaultUser", "User") .WithMany("EmailClaims") - .HasForeignKey("UserId"); + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); b.Navigation("User"); }); From 17e4f614d89d663153775dd7d6b6c1cfab0cd7cc Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 26 Dec 2024 00:27:35 +0100 Subject: [PATCH 56/62] Refactor postgres docker setup and update docs (#190) --- .github/workflows/publish-docker-images.yml | 24 +++++--- docker-compose.build.yml | 18 +++--- docker-compose.yml | 58 +++++++++---------- docs/misc/release/create-new-release.md | 12 +++- install.sh | 1 + .../Databases/AliasServerDb/Dockerfile | 2 +- .../Databases/AliasServerDb/postgresql.conf | 0 7 files changed, 65 insertions(+), 50 deletions(-) rename Dockerfile.postgres => src/Databases/AliasServerDb/Dockerfile (64%) rename postgresql.conf => src/Databases/AliasServerDb/postgresql.conf (100%) diff --git a/.github/workflows/publish-docker-images.yml b/.github/workflows/publish-docker-images.yml index a97f950a..4dee4a89 100644 --- a/.github/workflows/publish-docker-images.yml +++ b/.github/workflows/publish-docker-images.yml @@ -38,6 +38,14 @@ jobs: with: images: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }} + - name: Build and push Postgres image + uses: docker/build-push-action@v5 + with: + context: . + file: src/Databases/AliasServerDb/Dockerfile + push: true + tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-postgres:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-postgres:${{ github.ref_name }} + - name: Build and push API image uses: docker/build-push-action@v5 with: @@ -62,6 +70,14 @@ jobs: push: true tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-admin:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-admin:${{ github.ref_name }} + - name: Build and push Reverse Proxy image + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + push: true + tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-reverse-proxy:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-reverse-proxy:${{ github.ref_name }} + - name: Build and push SMTP image uses: docker/build-push-action@v5 with: @@ -78,14 +94,6 @@ jobs: push: true tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-task-runner:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-task-runner:${{ github.ref_name }} - - name: Build and push Reverse Proxy image - uses: docker/build-push-action@v5 - with: - context: . - file: Dockerfile - push: true - tags: ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-reverse-proxy:latest,${{ env.REGISTRY }}/${{ env.REPO_LOWER }}-reverse-proxy:${{ github.ref_name }} - - name: Build and push InstallCli image uses: docker/build-push-action@v5 with: diff --git a/docker-compose.build.yml b/docker-compose.build.yml index cd1382bf..bf873e9c 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -1,9 +1,9 @@ services: - reverse-proxy: - image: aliasvault-reverse-proxy + postgres: + image: aliasvault-postgres build: context: . - dockerfile: Dockerfile + dockerfile: src/Databases/AliasServerDb/Dockerfile client: image: aliasvault-client @@ -23,6 +23,12 @@ services: context: . dockerfile: src/AliasVault.Admin/Dockerfile + reverse-proxy: + image: aliasvault-reverse-proxy + build: + context: . + dockerfile: Dockerfile + smtp: image: aliasvault-smtp build: @@ -34,9 +40,3 @@ services: build: context: . dockerfile: src/Services/AliasVault.TaskRunner/Dockerfile - - postgres: - image: aliasvault-postgres - build: - context: . - dockerfile: Dockerfile.postgres diff --git a/docker-compose.yml b/docker-compose.yml index 220e5cb0..c4f553e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,17 @@ services: - reverse-proxy: - image: ghcr.io/lanedirt/aliasvault-reverse-proxy:latest - ports: - - "${HTTP_PORT:-80}:80" - - "${HTTPS_PORT:-443}:443" + postgres: + image: ghcr.io/lanedirt/aliasvault-postgres:latest volumes: - - ./certificates/ssl:/etc/nginx/ssl:rw - - ./certificates/letsencrypt:/etc/nginx/ssl-letsencrypt:rw - - ./certificates/letsencrypt/www:/var/www/certbot:rw - depends_on: - - admin - - client - - api - - smtp - restart: always + - ./database/postgres:/var/lib/postgresql/data:rw env_file: - .env + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U aliasvault"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s client: image: ghcr.io/lanedirt/aliasvault-client:latest @@ -61,6 +57,24 @@ services: environment: ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" + reverse-proxy: + image: ghcr.io/lanedirt/aliasvault-reverse-proxy:latest + ports: + - "${HTTP_PORT:-80}:80" + - "${HTTPS_PORT:-443}:443" + volumes: + - ./certificates/ssl:/etc/nginx/ssl:rw + - ./certificates/letsencrypt:/etc/nginx/ssl-letsencrypt:rw + - ./certificates/letsencrypt/www:/var/www/certbot:rw + depends_on: + - admin + - client + - api + - smtp + restart: always + env_file: + - .env + smtp: image: ghcr.io/lanedirt/aliasvault-smtp:latest ports: @@ -90,18 +104,4 @@ services: postgres: condition: service_healthy environment: - ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" - - postgres: - image: ghcr.io/lanedirt/aliasvault-postgres:latest - volumes: - - ./database/postgres:/var/lib/postgresql/data:rw - env_file: - - .env - restart: always - healthcheck: - test: ["CMD-SHELL", "pg_isready -U aliasvault"] - interval: 5s - timeout: 5s - retries: 5 - start_period: 10s + ConnectionStrings__AliasServerDbContext: "Host=postgres;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD}" \ No newline at end of file diff --git a/docs/misc/release/create-new-release.md b/docs/misc/release/create-new-release.md index 5fac8fab..cc0bc68f 100644 --- a/docs/misc/release/create-new-release.md +++ b/docs/misc/release/create-new-release.md @@ -10,10 +10,16 @@ nav_order: 1 Follow the steps in the checklist below to prepare a new release. +## Versioning - [ ] Update ./src/Shared/AliasVault.Shared.Core/AppInfo.cs and update major/minor/patch to the new version. This version will be shown in the client and admin app footer. - [ ] Update ./install.sh `@version` in header if the install script has changed. This allows the install script to self-update when running ./install.sh update command on default installations. -- [ ] Update README screenshots if applicable -- [ ] Update README current/upcoming features -Optional steps: +## Docker Images +If docker containers have been added or removed: +- [ ] Verify that `publish-docker-images.yml` contains all docker images that need to be published. +- [ ] Update `install.sh` and verify that the `images()` array that takes care of pulling the images from the GitHub Container Registry is updated. + +## Documentation - [ ] Update /docs instructions if any changes have been made to the setup process +- [ ] Update README screenshots if applicable +- [ ] Update README current/upcoming features diff --git a/install.sh b/install.sh index 8893875c..13b71261 100755 --- a/install.sh +++ b/install.sh @@ -1452,6 +1452,7 @@ handle_install_version() { printf "${CYAN}> Installing version: ${target_version}${NC}\n" images=( + "${GITHUB_CONTAINER_REGISTRY}-postgres:${target_version}" "${GITHUB_CONTAINER_REGISTRY}-reverse-proxy:${target_version}" "${GITHUB_CONTAINER_REGISTRY}-api:${target_version}" "${GITHUB_CONTAINER_REGISTRY}-client:${target_version}" diff --git a/Dockerfile.postgres b/src/Databases/AliasServerDb/Dockerfile similarity index 64% rename from Dockerfile.postgres rename to src/Databases/AliasServerDb/Dockerfile index 9ccbd44c..ee6f092c 100644 --- a/Dockerfile.postgres +++ b/src/Databases/AliasServerDb/Dockerfile @@ -1,6 +1,6 @@ FROM postgres:16-alpine # Add any custom PostgreSQL configurations if needed -COPY postgresql.conf /etc/postgresql/postgresql.conf +COPY src/Databases/AliasServerDb/postgresql.conf /etc/postgresql/postgresql.conf CMD ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"] \ No newline at end of file diff --git a/postgresql.conf b/src/Databases/AliasServerDb/postgresql.conf similarity index 100% rename from postgresql.conf rename to src/Databases/AliasServerDb/postgresql.conf From 180de219c8b12d6c20f6c240dd94c4f0a80f3a78 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 26 Dec 2024 00:39:42 +0100 Subject: [PATCH 57/62] Update installer to look at latest release instead of main (#190) --- install.sh | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/install.sh b/install.sh index 13b71261..f6ac1a89 100755 --- a/install.sh +++ b/install.sh @@ -1,12 +1,10 @@ #!/bin/bash -# @version 0.10.0 +# @version 0.9.4 # Repository information used for downloading files and images from GitHub REPO_OWNER="lanedirt" REPO_NAME="AliasVault" -REPO_BRANCH="main" GITHUB_RAW_URL_REPO="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}" -GITHUB_RAW_URL_REPO_BRANCH="$GITHUB_RAW_URL_REPO/$REPO_BRANCH" GITHUB_CONTAINER_REGISTRY="ghcr.io/$(echo "$REPO_OWNER" | tr '[:upper:]' '[:lower:]')/$(echo "$REPO_NAME" | tr '[:upper:]' '[:lower:]')" # Required files and directories @@ -1324,8 +1322,15 @@ compare_versions() { check_install_script_update() { printf "${CYAN}> Checking for install script updates...${NC}\n" - # Download latest install.sh to temporary file - if ! curl -sSf "${GITHUB_RAW_URL_REPO_BRANCH}/install.sh" -o "install.sh.tmp"; then + # Get latest release version + local latest_version=$(curl -s "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + + if [ -z "$latest_version" ]; then + printf "${RED}> Failed to check for install script updates. Continuing with current version.${NC}\n" + return 1 + fi + + if ! curl -sSf "${GITHUB_RAW_URL_REPO}/${latest_version}/install.sh" -o "install.sh.tmp"; then printf "${RED}> Failed to check for install script updates. Continuing with current version.${NC}\n" rm -f install.sh.tmp return 1 @@ -1493,8 +1498,8 @@ configure_dev_database() { printf "\n" if [ ! -f "docker-compose.dev.yml" ]; then - printf "${CYAN}> Downloading docker-compose.dev.yml...${NC}\n" - curl -sSf "${GITHUB_RAW_URL_REPO_BRANCH}/docker-compose.dev.yml" -o "docker-compose.dev.yml" + printf "${RED}> The docker-compose.dev.yml file is missing. This file is required to start the development database. Please checkout the full GitHub repository and try again.${NC}\n" + return 1 fi # Check if direct option was provided @@ -1605,7 +1610,7 @@ print_dev_db_details() { printf "${MAGENTA}=========================================================${NC}\n" } -# Function to handle database migration +# Function to handle database migration. This is a one-time operation necessary when upgrading from <= 0.9.x to 0.10.0+ and only needs to be run once. handle_migrate_db() { printf "${YELLOW}+++ Database Migration Tool +++${NC}\n" printf "\n" From d77c28184c6972554ceee5e0a51f0b248b748d34 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 26 Dec 2024 01:13:40 +0100 Subject: [PATCH 58/62] Refactor (#190) --- .github/workflows/dotnet-e2e-admin-tests.yml | 12 ++- .github/workflows/dotnet-e2e-client-tests.yml | 12 ++- .github/workflows/dotnet-e2e-misc-tests.yml | 12 ++- .../workflows/dotnet-integration-tests.yml | 12 ++- docs/misc/release/create-new-release.md | 4 +- .../AliasVault.InstallCli/Program.cs | 90 ++++++++++++------- 6 files changed, 100 insertions(+), 42 deletions(-) diff --git a/.github/workflows/dotnet-e2e-admin-tests.yml b/.github/workflows/dotnet-e2e-admin-tests.yml index 70ab08b9..040ee69e 100644 --- a/.github/workflows/dotnet-e2e-admin-tests.yml +++ b/.github/workflows/dotnet-e2e-admin-tests.yml @@ -30,8 +30,16 @@ jobs: - name: Wait for database to be up run: | - # Wait for a few seconds - sleep 5 + timeout=30 + while ! nc -z localhost 5433; do + if [ "$timeout" -le "0" ]; then + echo "Timed out waiting for database" + exit 1 + fi + echo "Waiting for database to be available on port 5433..." + sleep 1 + timeout=$((timeout-1)) + done - name: Ensure browsers are installed run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps diff --git a/.github/workflows/dotnet-e2e-client-tests.yml b/.github/workflows/dotnet-e2e-client-tests.yml index b10b7a18..bfbfcdd5 100644 --- a/.github/workflows/dotnet-e2e-client-tests.yml +++ b/.github/workflows/dotnet-e2e-client-tests.yml @@ -34,8 +34,16 @@ jobs: - name: Wait for database to be up run: | - # Wait for a few seconds - sleep 5 + timeout=30 + while ! nc -z localhost 5433; do + if [ "$timeout" -le "0" ]; then + echo "Timed out waiting for database" + exit 1 + fi + echo "Waiting for database to be available on port 5433..." + sleep 1 + timeout=$((timeout-1)) + done - name: Ensure browsers are installed run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps diff --git a/.github/workflows/dotnet-e2e-misc-tests.yml b/.github/workflows/dotnet-e2e-misc-tests.yml index d0d132a5..79cf2dc8 100644 --- a/.github/workflows/dotnet-e2e-misc-tests.yml +++ b/.github/workflows/dotnet-e2e-misc-tests.yml @@ -30,8 +30,16 @@ jobs: - name: Wait for database to be up run: | - # Wait for a few seconds - sleep 5 + timeout=30 + while ! nc -z localhost 5433; do + if [ "$timeout" -le "0" ]; then + echo "Timed out waiting for database" + exit 1 + fi + echo "Waiting for database to be available on port 5433..." + sleep 1 + timeout=$((timeout-1)) + done - name: Ensure browsers are installed run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps diff --git a/.github/workflows/dotnet-integration-tests.yml b/.github/workflows/dotnet-integration-tests.yml index add403b9..754d43ba 100644 --- a/.github/workflows/dotnet-integration-tests.yml +++ b/.github/workflows/dotnet-integration-tests.yml @@ -30,8 +30,16 @@ jobs: - name: Wait for database to be up run: | - # Wait for a few seconds - sleep 5 + timeout=30 + while ! nc -z localhost 5433; do + if [ "$timeout" -le "0" ]; then + echo "Timed out waiting for database" + exit 1 + fi + echo "Waiting for database to be available on port 5433..." + sleep 1 + timeout=$((timeout-1)) + done - name: Run integration tests run: dotnet test src/Tests/AliasVault.IntegrationTests --no-build --verbosity normal diff --git a/docs/misc/release/create-new-release.md b/docs/misc/release/create-new-release.md index cc0bc68f..4c8d3dc2 100644 --- a/docs/misc/release/create-new-release.md +++ b/docs/misc/release/create-new-release.md @@ -12,11 +12,11 @@ Follow the steps in the checklist below to prepare a new release. ## Versioning - [ ] Update ./src/Shared/AliasVault.Shared.Core/AppInfo.cs and update major/minor/patch to the new version. This version will be shown in the client and admin app footer. -- [ ] Update ./install.sh `@version` in header if the install script has changed. This allows the install script to self-update when running ./install.sh update command on default installations. +- [ ] Update ./install.sh `@version` in header if the install script has changed. This allows the install script to self-update when running the `./install.sh update` command on default installations. ## Docker Images If docker containers have been added or removed: -- [ ] Verify that `publish-docker-images.yml` contains all docker images that need to be published. +- [ ] Verify that `.github/workflows/publish-docker-images.yml` contains references to all docker images that need to be published. - [ ] Update `install.sh` and verify that the `images()` array that takes care of pulling the images from the GitHub Container Registry is updated. ## Documentation diff --git a/src/Utilities/AliasVault.InstallCli/Program.cs b/src/Utilities/AliasVault.InstallCli/Program.cs index ddd9efee..9d889ff5 100644 --- a/src/Utilities/AliasVault.InstallCli/Program.cs +++ b/src/Utilities/AliasVault.InstallCli/Program.cs @@ -8,6 +8,7 @@ using AliasServerDb; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; // Add return type for top-level statements return await Run(args); @@ -233,26 +234,7 @@ private static async Task MigrateTable( foreach (var item in items) { - // Check each property for MaxLength annotation - foreach (var property in entityType!.GetProperties()) - { - // Only process string properties - if (property.ClrType == typeof(string)) - { - var maxLength = property.GetMaxLength(); - if (maxLength.HasValue) - { - var propertyInfo = typeof(T).GetProperty(property.Name); - var value = propertyInfo?.GetValue(item) as string; - - if (value?.Length > maxLength.Value) - { - propertyInfo?.SetValue(item, value.Substring(0, maxLength.Value)); - Console.WriteLine($"Truncated {property.Name} in {tableName} from {value.Length} to {maxLength.Value} characters"); - } - } - } - } + HandleMaxLengthConstraints(item, entityType!, tableName); } const int batchSize = 50; @@ -272,20 +254,10 @@ private static async Task MigrateTable( } } - // Only reset sequence if requested + // Handle sequence reset logic... if (resetSequence && destinationContext.Database.ProviderName == "Npgsql.EntityFrameworkCore.PostgreSQL") { - var tablePgName = destinationContext.Model.FindEntityType(typeof(T))?.GetTableName(); - if (!string.IsNullOrEmpty(tablePgName)) - { - var schema = destinationContext.Model.FindEntityType(typeof(T))?.GetSchema() ?? "public"; - var sql = $""" - SELECT setval(pg_get_serial_sequence('{schema}."{tablePgName}"', 'Id'), - (SELECT COALESCE(MAX("Id"::integer), 0) + 1 FROM {schema}."{tablePgName}"), false); - """; - await destinationContext.Database.ExecuteSqlRawAsync(sql); - Console.WriteLine($"Reset sequence for {tableName}"); - } + await ResetSequence(destinationContext, tableName); } } @@ -296,6 +268,60 @@ SELECT setval(pg_get_serial_sequence('{schema}."{tablePgName}"', 'Id'), } } + /// + /// Handles max length constraints for string properties in an entity. + /// + /// The entity type containing the properties to be processed. + /// The entity instance containing the properties to be processed. + /// The entity type to be processed. + /// The name of the table being processed (for logging purposes). + private static void HandleMaxLengthConstraints(T item, IEntityType entityType, string tableName) + where T : class + { + foreach (var property in entityType.GetProperties()) + { + // Only process string properties + if (property.ClrType == typeof(string)) + { + var maxLength = property.GetMaxLength(); + if (maxLength.HasValue) + { + var propertyInfo = typeof(T).GetProperty(property.Name); + var value = propertyInfo?.GetValue(item) as string; + + if (value?.Length > maxLength.Value) + { + propertyInfo!.SetValue(item, value.Substring(0, maxLength.Value)); + Console.WriteLine($"Truncated {property.Name} in {tableName} from {value.Length} to {maxLength.Value} characters"); + } + } + } + } + } + + /// + /// Resets the sequence for a table in the PostgreSQL database. + /// + /// The entity type of the table being reset. + /// The destination database context. + /// The name of the table being reset (for logging purposes). + /// A task representing the asynchronous operation. + private static async Task ResetSequence(DbContext destinationContext, string tableName) + where T : class + { + var tablePgName = destinationContext.Model.FindEntityType(typeof(T))?.GetTableName(); + if (!string.IsNullOrEmpty(tablePgName)) + { + var schema = destinationContext.Model.FindEntityType(typeof(T))?.GetSchema() ?? "public"; + var sql = $""" + SELECT setval(pg_get_serial_sequence('{schema}."{tablePgName}"', 'Id'), + (SELECT COALESCE(MAX("Id"::integer), 0) + 1 FROM {schema}."{tablePgName}"), false); + """; + await destinationContext.Database.ExecuteSqlRawAsync(sql); + Console.WriteLine($"Reset sequence for {tableName}"); + } + } + /// /// Handles a concurrency conflict by updating the original values with the database values. /// From 9d1923d3ea8a6f383e0a76bac9cebefb493a3844 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 26 Dec 2024 01:26:43 +0100 Subject: [PATCH 59/62] Refactor dev db start (#190) --- .github/workflows/dotnet-e2e-admin-tests.yml | 13 ------------- .github/workflows/dotnet-e2e-client-tests.yml | 13 ------------- .github/workflows/dotnet-e2e-misc-tests.yml | 13 ------------- .github/workflows/dotnet-integration-tests.yml | 13 ------------- docker-compose.dev.yml | 2 +- install.sh | 4 ++-- 6 files changed, 3 insertions(+), 55 deletions(-) diff --git a/.github/workflows/dotnet-e2e-admin-tests.yml b/.github/workflows/dotnet-e2e-admin-tests.yml index 040ee69e..ad553d8f 100644 --- a/.github/workflows/dotnet-e2e-admin-tests.yml +++ b/.github/workflows/dotnet-e2e-admin-tests.yml @@ -28,19 +28,6 @@ jobs: - name: Start dev database run: ./install.sh configure-dev-db start - - name: Wait for database to be up - run: | - timeout=30 - while ! nc -z localhost 5433; do - if [ "$timeout" -le "0" ]; then - echo "Timed out waiting for database" - exit 1 - fi - echo "Waiting for database to be available on port 5433..." - sleep 1 - timeout=$((timeout-1)) - done - - name: Ensure browsers are installed run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps diff --git a/.github/workflows/dotnet-e2e-client-tests.yml b/.github/workflows/dotnet-e2e-client-tests.yml index bfbfcdd5..2107fff3 100644 --- a/.github/workflows/dotnet-e2e-client-tests.yml +++ b/.github/workflows/dotnet-e2e-client-tests.yml @@ -32,19 +32,6 @@ jobs: - name: Start dev database run: ./install.sh configure-dev-db start - - name: Wait for database to be up - run: | - timeout=30 - while ! nc -z localhost 5433; do - if [ "$timeout" -le "0" ]; then - echo "Timed out waiting for database" - exit 1 - fi - echo "Waiting for database to be available on port 5433..." - sleep 1 - timeout=$((timeout-1)) - done - - name: Ensure browsers are installed run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps diff --git a/.github/workflows/dotnet-e2e-misc-tests.yml b/.github/workflows/dotnet-e2e-misc-tests.yml index 79cf2dc8..4d0088c6 100644 --- a/.github/workflows/dotnet-e2e-misc-tests.yml +++ b/.github/workflows/dotnet-e2e-misc-tests.yml @@ -28,19 +28,6 @@ jobs: - name: Start dev database run: ./install.sh configure-dev-db start - - name: Wait for database to be up - run: | - timeout=30 - while ! nc -z localhost 5433; do - if [ "$timeout" -le "0" ]; then - echo "Timed out waiting for database" - exit 1 - fi - echo "Waiting for database to be available on port 5433..." - sleep 1 - timeout=$((timeout-1)) - done - - name: Ensure browsers are installed run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net9.0/playwright.ps1 install --with-deps diff --git a/.github/workflows/dotnet-integration-tests.yml b/.github/workflows/dotnet-integration-tests.yml index 754d43ba..846a9cb0 100644 --- a/.github/workflows/dotnet-integration-tests.yml +++ b/.github/workflows/dotnet-integration-tests.yml @@ -28,18 +28,5 @@ jobs: - name: Start dev database run: ./install.sh configure-dev-db start - - name: Wait for database to be up - run: | - timeout=30 - while ! nc -z localhost 5433; do - if [ "$timeout" -le "0" ]; then - echo "Timed out waiting for database" - exit 1 - fi - echo "Waiting for database to be available on port 5433..." - sleep 1 - timeout=$((timeout-1)) - done - - name: Run integration tests run: dotnet test src/Tests/AliasVault.IntegrationTests --no-build --verbosity normal diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 29dc7586..c4f83463 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -5,7 +5,7 @@ services: - "5433:5432" volumes: - ./database/postgres-dev:/var/lib/postgresql/data:rw - - ./postgresql.conf:/etc/postgresql/postgresql.conf + - ./src/Databases/AliasServerDb/postgresql.conf:/etc/postgresql/postgresql.conf environment: - POSTGRES_DB=aliasvault - POSTGRES_USER=aliasvault diff --git a/install.sh b/install.sh index f6ac1a89..72c865fa 100755 --- a/install.sh +++ b/install.sh @@ -1510,7 +1510,7 @@ configure_dev_database() { printf "${YELLOW}> Development database is already running.${NC}\n" else printf "${CYAN}> Starting development database...${NC}\n" - docker compose -p aliasvault-dev -f docker-compose.dev.yml up -d + docker compose -p aliasvault-dev -f docker-compose.dev.yml up -d --wait --wait-timeout 60 printf "${GREEN}> Development database started successfully.${NC}\n" fi print_dev_db_details @@ -1564,7 +1564,7 @@ configure_dev_database() { printf "${YELLOW}> Development database is already running.${NC}\n" else printf "${CYAN}> Starting development database...${NC}\n" - docker compose -p aliasvault-dev -f docker-compose.dev.yml up -d + docker compose -p aliasvault-dev -f docker-compose.dev.yml up -d --wait --wait-timeout 60 printf "${GREEN}> Development database started successfully.${NC}\n" fi print_dev_db_details From e577d6fee42936fa852397c177de6525eecb804e Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 26 Dec 2024 01:30:32 +0100 Subject: [PATCH 60/62] Print warnring but do not exit on image pull fail (#190) --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 72c865fa..4bb7f374 100755 --- a/install.sh +++ b/install.sh @@ -1469,9 +1469,9 @@ handle_install_version() { for image in "${images[@]}"; do printf "${CYAN}> Pulling $image...${NC}\n" if [ "$VERBOSE" = true ]; then - docker pull $image || { printf "${RED}> Failed to pull image: $image${NC}\n"; exit 1; } + docker pull $image || printf "${YELLOW}> Warning: Failed to pull image: $image - continuing anyway${NC}\n" else - docker pull $image > /dev/null 2>&1 || { printf "${RED}> Failed to pull image: $image${NC}\n"; exit 1; } + docker pull $image > /dev/null 2>&1 || printf "${YELLOW}> Warning: Failed to pull image: $image - continuing anyway${NC}\n" fi done From 329ae185ad1035db86480ef1b0acee1756b541f5 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 28 Dec 2024 14:28:09 +0100 Subject: [PATCH 61/62] Update docs (#190) --- docs/misc/release/create-new-release.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/misc/release/create-new-release.md b/docs/misc/release/create-new-release.md index 4c8d3dc2..2f27d30b 100644 --- a/docs/misc/release/create-new-release.md +++ b/docs/misc/release/create-new-release.md @@ -19,6 +19,9 @@ If docker containers have been added or removed: - [ ] Verify that `.github/workflows/publish-docker-images.yml` contains references to all docker images that need to be published. - [ ] Update `install.sh` and verify that the `images()` array that takes care of pulling the images from the GitHub Container Registry is updated. +## Manual Testing (since v0.10.0+) +- [ ] Verify that the db migration from SQLite to PostgreSQL works. This needs to be tested manually until the SQLite support is removed. Test with: `./install.sh db-migrate` on an existing installation that has a SQLite database in `./database/AliasServerDb.sqlite`. + ## Documentation - [ ] Update /docs instructions if any changes have been made to the setup process - [ ] Update README screenshots if applicable From d3518eca6cde35884d6904c8cc320669281a4ba7 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Sat, 28 Dec 2024 15:52:33 +0100 Subject: [PATCH 62/62] Update install.sh add install.sh version check to install command itself (#190) --- install.sh | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/install.sh b/install.sh index 4bb7f374..06ba9769 100755 --- a/install.sh +++ b/install.sh @@ -318,6 +318,44 @@ handle_docker_compose() { return 0 } +# Function to check and update install.sh for specific version +check_install_script_version() { + local target_version="$1" + printf "${CYAN}> Checking install script version for ${target_version}...${NC}\n" + + # Get remote install.sh for target version + if ! curl -sSf "${GITHUB_RAW_URL_REPO}/${target_version}/install.sh" -o "install.sh.tmp"; then + printf "${RED}> Failed to check install script version. Continuing with current version.${NC}\n" + rm -f install.sh.tmp + return 1 + fi + + # Get versions + local current_version=$(extract_version "install.sh") + local target_script_version=$(extract_version "install.sh.tmp") + + # Check if versions could be extracted + if [ -z "$current_version" ] || [ -z "$target_script_version" ]; then + printf "${YELLOW}> Could not determine script versions. Falling back to file comparison...${NC}\n" + if ! cmp -s "install.sh" "install.sh.tmp"; then + printf "${YELLOW}> Install script needs updating to match version ${target_version}${NC}\n" + return 2 + fi + else + printf "${CYAN}> Current install script version: ${current_version}${NC}\n" + printf "${CYAN}> Target install script version: ${target_script_version}${NC}\n" + + if [ "$current_version" != "$target_script_version" ]; then + printf "${YELLOW}> Install script needs updating to match version ${target_version}${NC}\n" + return 2 + fi + fi + + printf "${GREEN}> Install script is up to date for version ${target_version}.${NC}\n" + rm -f install.sh.tmp + return 0 +} + # Function to print the logo print_logo() { printf "${MAGENTA}" @@ -1430,6 +1468,40 @@ handle_install_version() { # Initialize workspace which makes sure all required directories and files exist initialize_workspace + # Check if install script needs updating for this version + check_install_script_version "$target_version" + local check_result=$? + + if [ $check_result -eq 2 ]; then + if [ "$FORCE_YES" = true ]; then + printf "${CYAN}> Updating install script to match version ${target_version}...${NC}\n" + else + printf "${YELLOW}> A different version of the install script is required for installing version ${target_version}.${NC}\n" + read -p "Would you like to self-update the install script before proceeding? [Y/n]: " reply + if [[ $reply =~ ^[Nn]$ ]]; then + printf "${YELLOW}> Continuing with current install script version.${NC}\n" + rm -f install.sh.tmp + fi + fi + + if [ "$FORCE_YES" = true ] || [[ ! $reply =~ ^[Nn]$ ]]; then + # Create backup of current script + cp "install.sh" "install.sh.backup" + + if mv "install.sh.tmp" "install.sh"; then + chmod +x "install.sh" + printf "${GREEN}> Install script updated successfully.${NC}\n" + printf "${GREEN}> Backup of previous version saved as install.sh.backup${NC}\n" + printf "${YELLOW}> Please run the same install command again to continue with the installation.${NC}\n" + exit 0 + else + printf "${RED}> Failed to update install script. Continuing with current version.${NC}\n" + mv "install.sh.backup" "install.sh" + rm -f install.sh.tmp + fi + fi + fi + # Update docker-compose files with correct version so we pull the correct images handle_docker_compose "$target_version"