diff --git a/Journal.Core/Dtos/CommandDtos.cs b/Journal.Core/Dtos/CommandDtos.cs index 4596ec5..8673e3a 100644 --- a/Journal.Core/Dtos/CommandDtos.cs +++ b/Journal.Core/Dtos/CommandDtos.cs @@ -15,7 +15,7 @@ internal sealed record EntryTemplateLoadPayload(string FilePath); internal sealed record EntryTemplateDeletePayload(string FilePath); public sealed record EntryTemplateLoadResult(string FileName, string FilePath, string Content); public sealed record EntryTemplateSavePayload(string Name, string Content, string? FilePath = null); -internal sealed record DatabasePayload(string Password, string? DataDirectory = null); +internal sealed record DatabasePayload(string Password); internal sealed record AiSummarizeEntryPayload(string Content, string? FileStem = null); internal sealed record AiSummarizeAllPayload(List? Entries); internal sealed record AiChatPayload(string Prompt); diff --git a/Journal.Core/Dtos/DatabaseDtos.cs b/Journal.Core/Dtos/DatabaseDtos.cs index 3c587e7..163b07e 100644 --- a/Journal.Core/Dtos/DatabaseDtos.cs +++ b/Journal.Core/Dtos/DatabaseDtos.cs @@ -6,13 +6,11 @@ public sealed record JournalDatabaseStatus( int Iterations, string KeyDerivation, IReadOnlyList SchemaTables, - string SchemaBootstrapPath, bool RuntimeReady, string RuntimeMessage); public sealed record JournalDatabaseHydrationResult( string DatabasePath, - string SchemaBootstrapPath, int EntryFilesProcessed, bool RuntimeReady, string Message); diff --git a/Journal.Core/Entry.cs b/Journal.Core/Entry.cs index 59306fe..23e4898 100644 --- a/Journal.Core/Entry.cs +++ b/Journal.Core/Entry.cs @@ -342,16 +342,6 @@ public class Entry( _databaseSession.SetPassword(loadPayload.Password); result = loaded; break; - case "vault.save_current_month": - var saveCurrentPayload = DeserializePayload(cmd.Payload); - if (saveCurrentPayload is null) - return Error("Missing or invalid payload"); - _vaultStorage.RebuildAllVaults( - saveCurrentPayload.Password, - saveCurrentPayload.VaultDirectory, - ResolveVaultStorageDirectory()); - result = true; - break; case "vault.rebuild_all": var rebuildPayload = DeserializePayload(cmd.Payload); if (rebuildPayload is null) @@ -373,20 +363,25 @@ public class Entry( var dbStatusPayload = DeserializePayload(cmd.Payload); if (dbStatusPayload is null || string.IsNullOrWhiteSpace(dbStatusPayload.Password)) return Error("Missing or invalid payload"); - result = _database.GetStatus(dbStatusPayload.Password, dbStatusPayload.DataDirectory); + result = _database.GetStatus(dbStatusPayload.Password); break; case "db.initialize_schema": var dbInitPayload = DeserializePayload(cmd.Payload); - if (dbInitPayload is null) + if (dbInitPayload is null || string.IsNullOrWhiteSpace(dbInitPayload.Password)) return Error("Missing or invalid payload"); - var schemaPath = _database.WriteSchemaBootstrap(dbInitPayload.DataDirectory); - result = new { schemaPath }; + var initResult = _database.HydrateWorkspace(dbInitPayload.Password); + result = new + { + initialized = initResult.RuntimeReady, + databasePath = initResult.DatabasePath, + initResult.Message + }; break; case "db.hydrate_workspace": var dbHydratePayload = DeserializePayload(cmd.Payload); if (dbHydratePayload is null || string.IsNullOrWhiteSpace(dbHydratePayload.Password)) return Error("Missing or invalid payload"); - result = _database.HydrateWorkspace(dbHydratePayload.Password, dbHydratePayload.DataDirectory); + result = _database.HydrateWorkspace(dbHydratePayload.Password); _databaseSession.SetPassword(dbHydratePayload.Password); break; default: @@ -450,7 +445,7 @@ public class Entry( if (!VaultSyncActions.Contains(action)) return; - if (!_databaseSession.TryGetSession(out var password, out _)) + if (!_databaseSession.TryGetSession(out var password)) return; try diff --git a/Journal.Core/Services/Database/DatabaseSessionService.cs b/Journal.Core/Services/Database/DatabaseSessionService.cs index 67e3f11..c8b7163 100644 --- a/Journal.Core/Services/Database/DatabaseSessionService.cs +++ b/Journal.Core/Services/Database/DatabaseSessionService.cs @@ -7,7 +7,6 @@ public sealed class DatabaseSessionService(IJournalDatabaseService database) : I private readonly IJournalDatabaseService _database = database; private readonly Lock _lock = new(); private string? _password; - private string? _dataDirectory; private SqliteConnection? _connection; public bool IsUnlocked @@ -18,38 +17,34 @@ public sealed class DatabaseSessionService(IJournalDatabaseService database) : I } } - public void SetPassword(string password, string? dataDirectory = null) + public void SetPassword(string password) { if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Password cannot be empty.", nameof(password)); lock (_lock) { - if (_connection is not null && - (_password != password || _dataDirectory != dataDirectory)) + if (_connection is not null && _password != password) { _connection.Dispose(); _connection = null; } _password = password; - _dataDirectory = dataDirectory; } } - public bool TryGetSession(out string password, out string? dataDirectory) + public bool TryGetSession(out string password) { lock (_lock) { if (string.IsNullOrWhiteSpace(_password)) { password = ""; - dataDirectory = null; return false; } password = _password; - dataDirectory = _dataDirectory; return true; } } @@ -65,7 +60,7 @@ public sealed class DatabaseSessionService(IJournalDatabaseService database) : I if (_connection is not null) return _connection; - _connection = _database.OpenEncryptedConnection(_password, _dataDirectory); + _connection = _database.OpenEncryptedConnection(_password); _database.EnsureSchema(_connection); return _connection; } diff --git a/Journal.Core/Services/Database/IDatabaseSessionService.cs b/Journal.Core/Services/Database/IDatabaseSessionService.cs index 6c29e85..379d0b6 100644 --- a/Journal.Core/Services/Database/IDatabaseSessionService.cs +++ b/Journal.Core/Services/Database/IDatabaseSessionService.cs @@ -5,8 +5,8 @@ namespace Journal.Core.Services.Database; public interface IDatabaseSessionService { bool IsUnlocked { get; } - void SetPassword(string password, string? dataDirectory = null); - bool TryGetSession(out string password, out string? dataDirectory); + void SetPassword(string password); + bool TryGetSession(out string password); SqliteConnection GetConnection(); void CloseConnection(); } diff --git a/Journal.Core/Services/Database/IJournalDatabaseService.cs b/Journal.Core/Services/Database/IJournalDatabaseService.cs index 8d240fd..77ef2e9 100644 --- a/Journal.Core/Services/Database/IJournalDatabaseService.cs +++ b/Journal.Core/Services/Database/IJournalDatabaseService.cs @@ -5,13 +5,12 @@ namespace Journal.Core.Services.Database; public interface IJournalDatabaseService { - string GetDatabasePath(string? dataDirectory = null); + string GetDatabasePath(); byte[] DeriveDatabaseKey(string password); string BuildPragmaKeyStatement(string password); IReadOnlyDictionary GetSchemaStatements(); - SqliteConnection OpenEncryptedConnection(string password, string? dataDirectory = null); + SqliteConnection OpenEncryptedConnection(string password); void EnsureSchema(SqliteConnection connection); - string WriteSchemaBootstrap(string? dataDirectory = null); - JournalDatabaseStatus GetStatus(string password, string? dataDirectory = null); - JournalDatabaseHydrationResult HydrateWorkspace(string password, string? dataDirectory = null); + JournalDatabaseStatus GetStatus(string password); + JournalDatabaseHydrationResult HydrateWorkspace(string password); } diff --git a/Journal.Core/Services/Database/JournalDatabaseService.cs b/Journal.Core/Services/Database/JournalDatabaseService.cs index 8d15bbc..a042753 100644 --- a/Journal.Core/Services/Database/JournalDatabaseService.cs +++ b/Journal.Core/Services/Database/JournalDatabaseService.cs @@ -19,7 +19,7 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour private readonly IJournalConfigService _config = config; - public string GetDatabasePath(string? dataDirectory = null) + public string GetDatabasePath() { var directory = ResolveDatabaseDirectory(); @@ -133,51 +133,30 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour }; } - public string WriteSchemaBootstrap(string? dataDirectory = null) - { - var directory = ResolveDatabaseDirectory(); - Directory.CreateDirectory(directory); - - var bootstrapPath = Path.GetFullPath(Path.Combine(directory, "journal_schema.sql")); - var statements = GetSchemaStatements() - .Select(pair => $"-- {pair.Key}\n{pair.Value.Trim()}") - .ToArray(); - var content = string.Join("\n\n", statements) + "\n"; - File.WriteAllText(bootstrapPath, content); - return bootstrapPath; - } - - public JournalDatabaseStatus GetStatus(string password, string? dataDirectory = null) + public JournalDatabaseStatus GetStatus(string password) { var tables = GetSchemaStatements().Keys.OrderBy(x => x, StringComparer.Ordinal).ToArray(); - var bootstrapPath = WriteSchemaBootstrap(dataDirectory); - var runtime = ProbeRuntime(password, dataDirectory); + var runtime = ProbeRuntime(password); return new JournalDatabaseStatus( - DatabasePath: GetDatabasePath(dataDirectory), + DatabasePath: GetDatabasePath(), KeyLengthBytes: DeriveDatabaseKey(password).Length, Iterations: Iterations, KeyDerivation: "PBKDF2-HMAC-SHA256", SchemaTables: tables, - SchemaBootstrapPath: bootstrapPath, RuntimeReady: runtime.Ready, RuntimeMessage: runtime.Message); } - public JournalDatabaseHydrationResult HydrateWorkspace(string password, string? dataDirectory = null) + public JournalDatabaseHydrationResult HydrateWorkspace(string password) { - var directory = ResolveDatabaseDirectory(); - Directory.CreateDirectory(directory); - - using var connection = OpenEncryptedConnection(password, directory); + using var connection = OpenEncryptedConnection(password); EnsureSchema(connection); var runtimeReady = HasRequiredTables(connection); var entryDocumentsProcessed = CountEntryDocuments(connection); - var schemaPath = WriteSchemaBootstrap(directory); return new JournalDatabaseHydrationResult( - DatabasePath: GetDatabasePath(directory), - SchemaBootstrapPath: schemaPath, + DatabasePath: GetDatabasePath(), EntryFilesProcessed: entryDocumentsProcessed, RuntimeReady: runtimeReady, Message: runtimeReady @@ -200,14 +179,14 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour } } - public SqliteConnection OpenEncryptedConnection(string password, string? dataDirectory = null) + public SqliteConnection OpenEncryptedConnection(string password) { if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Password cannot be empty.", nameof(password)); EnsureSqliteInitialized(); - var connection = new SqliteConnection($"Data Source={GetDatabasePath(dataDirectory)};Mode=ReadWriteCreate;Pooling=False"); + var connection = new SqliteConnection($"Data Source={GetDatabasePath()};Mode=ReadWriteCreate;Pooling=False"); connection.Open(); using var keyCmd = connection.CreateCommand(); @@ -246,11 +225,11 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour return RequiredSchemaTables.All(existing.Contains); } - private (bool Ready, string Message) ProbeRuntime(string password, string? dataDirectory) + private (bool Ready, string Message) ProbeRuntime(string password) { try { - using var connection = OpenEncryptedConnection(password, dataDirectory); + using var connection = OpenEncryptedConnection(password); EnsureSchema(connection); var ready = HasRequiredTables(connection); return ready @@ -282,4 +261,5 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour return Path.GetFullPath(Path.Combine(_config.Current.VaultDirectory, "db")); } + } diff --git a/README.md b/README.md index e7fb223..c869ee9 100644 --- a/README.md +++ b/README.md @@ -381,12 +381,11 @@ Error: | `search.entries` | Search entries with filters | optional query/section/date/tags/types/checked/unchecked | | `vault.initialize` | Ensure vault directory exists | `payload.password`, `payload.vaultDirectory` | | `vault.load_all` | Restore encrypted SQLCipher DB snapshot from vault | `payload.password`, `payload.vaultDirectory` | -| `vault.save_current_month` | Alias for vault rebuild (DB snapshot persist) | `payload.password`, `payload.vaultDirectory` | | `vault.rebuild_all` | Persist encrypted SQLCipher DB snapshot to vault | `payload.password`, `payload.vaultDirectory` | | `vault.clear_data_directory` | No-op for SQLCipher-first mode (compat command) | — | -| `db.status` | DB key/schema compatibility snapshot | `payload.password`, optional `payload.dataDirectory` | -| `db.initialize_schema` | Write SQL schema bootstrap file | optional `payload.dataDirectory` | -| `db.hydrate_workspace` | Bootstrap DB + set session password | `payload.password`, optional `payload.dataDirectory` | +| `db.status` | DB key/schema compatibility snapshot | `payload.password` | +| `db.initialize_schema` | Initialize SQLCipher schema in the database file | `payload.password` | +| `db.hydrate_workspace` | Bootstrap DB + set session password | `payload.password` | | `config.get` | Return current config snapshot | — | | `ai.health` | AI provider health status | — | | `ai.summarize_entry` | Summarize one entry | `payload.content`, optional `payload.fileStem` |