Backend cleanup: remove schema file bootstrap and finalize SQLCipher-only DB init
This commit is contained in:
parent
f6ff9d2acb
commit
aafb08e63f
@ -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<string>? Entries);
|
||||
internal sealed record AiChatPayload(string Prompt);
|
||||
|
||||
@ -6,13 +6,11 @@ public sealed record JournalDatabaseStatus(
|
||||
int Iterations,
|
||||
string KeyDerivation,
|
||||
IReadOnlyList<string> SchemaTables,
|
||||
string SchemaBootstrapPath,
|
||||
bool RuntimeReady,
|
||||
string RuntimeMessage);
|
||||
|
||||
public sealed record JournalDatabaseHydrationResult(
|
||||
string DatabasePath,
|
||||
string SchemaBootstrapPath,
|
||||
int EntryFilesProcessed,
|
||||
bool RuntimeReady,
|
||||
string Message);
|
||||
|
||||
@ -342,16 +342,6 @@ public class Entry(
|
||||
_databaseSession.SetPassword(loadPayload.Password);
|
||||
result = loaded;
|
||||
break;
|
||||
case "vault.save_current_month":
|
||||
var saveCurrentPayload = DeserializePayload<VaultPayload>(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<VaultPayload>(cmd.Payload);
|
||||
if (rebuildPayload is null)
|
||||
@ -373,20 +363,25 @@ public class Entry(
|
||||
var dbStatusPayload = DeserializePayload<DatabasePayload>(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<DatabasePayload>(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<DatabasePayload>(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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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<string, string> 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);
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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` |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user