more of J's code organization.

This commit is contained in:
stan44 2026-02-23 21:34:49 -06:00
parent 8171744438
commit c01548b764
14 changed files with 61 additions and 114 deletions

View File

@ -37,4 +37,3 @@ desktop.ini
# macOS # macOS
.DS_Store .DS_Store
journal-master\journal\tls_registry_backup_before_fix.txt

View File

@ -7,44 +7,31 @@ using Journal.Core.Services;
namespace Journal.Core; namespace Journal.Core;
public class Entry public class Entry(
IFragmentService fragments,
IEntrySearchService entrySearch,
IVaultStorageService vaultStorage,
IJournalDatabaseService database,
IJournalConfigService config,
IAiService ai,
ISpeechBridgeService speech,
IEntryFileService entryFiles,
CommandLogger logger)
{ {
private readonly IFragmentService _fragments; private readonly IFragmentService _fragments = fragments;
private readonly IEntrySearchService _entrySearch; private readonly IEntrySearchService _entrySearch = entrySearch;
private readonly IVaultStorageService _vaultStorage; private readonly IVaultStorageService _vaultStorage = vaultStorage;
private readonly IJournalDatabaseService _database; private readonly IJournalDatabaseService _database = database;
private readonly IJournalConfigService _config; private readonly IJournalConfigService _config = config;
private readonly IAiService _ai; private readonly IAiService _ai = ai;
private readonly ISpeechBridgeService _speech; private readonly ISpeechBridgeService _speech = speech;
private readonly IEntryFileService _entryFiles; private readonly IEntryFileService _entryFiles = entryFiles;
private readonly CommandLogger _logger; private readonly CommandLogger _logger = logger;
private static readonly JsonSerializerOptions JsonOptions = new() private static readonly JsonSerializerOptions JsonOptions = new()
{ {
PropertyNameCaseInsensitive = true PropertyNameCaseInsensitive = true
}; };
public Entry(
IFragmentService fragments,
IEntrySearchService entrySearch,
IVaultStorageService vaultStorage,
IJournalDatabaseService database,
IJournalConfigService config,
IAiService ai,
ISpeechBridgeService speech,
IEntryFileService entryFiles,
CommandLogger logger)
{
_fragments = fragments;
_entrySearch = entrySearch;
_vaultStorage = vaultStorage;
_database = database;
_config = config;
_ai = ai;
_speech = speech;
_entryFiles = entryFiles;
_logger = logger;
}
public async Task RunAsync() public async Task RunAsync()
{ {
string? line; string? line;
@ -77,7 +64,7 @@ public class Entry
var correlationId = string.IsNullOrWhiteSpace(cmd.CorrelationId) var correlationId = string.IsNullOrWhiteSpace(cmd.CorrelationId)
? Guid.NewGuid().ToString("N") ? Guid.NewGuid().ToString("N")
: cmd.CorrelationId.Trim(); : cmd.CorrelationId.Trim();
_logger.LogStart(action, correlationId, cmd.Payload); CommandLogger.LogStart(action, correlationId, cmd.Payload);
object? result; object? result;
try try
@ -260,47 +247,47 @@ public class Entry
result = _database.HydrateWorkspace(dbHydratePayload.Password, dbHydratePayload.DataDirectory); result = _database.HydrateWorkspace(dbHydratePayload.Password, dbHydratePayload.DataDirectory);
break; break;
default: default:
_logger.LogFailure(action, correlationId, "unknown_action"); CommandLogger.LogFailure(action, correlationId, "unknown_action");
return Error($"Unknown action: {action}"); return Error($"Unknown action: {action}");
} }
} }
catch (JsonException) catch (JsonException)
{ {
_logger.LogFailure(action, correlationId, "invalid_payload_json"); CommandLogger.LogFailure(action, correlationId, "invalid_payload_json");
return Error("Missing or invalid payload"); return Error("Missing or invalid payload");
} }
catch (ValidationException ex) catch (ValidationException ex)
{ {
_logger.LogFailure(action, correlationId, "validation", ex.Message); CommandLogger.LogFailure(action, correlationId, "validation", ex.Message);
return Error(ex.Message); return Error(ex.Message);
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
_logger.LogFailure(action, correlationId, "argument", ex.Message); CommandLogger.LogFailure(action, correlationId, "argument", ex.Message);
return Error(ex.Message); return Error(ex.Message);
} }
catch (TimeoutException ex) catch (TimeoutException ex)
{ {
_logger.LogFailure(action, correlationId, "timeout", ex.Message); CommandLogger.LogFailure(action, correlationId, "timeout", ex.Message);
return Error(ex.Message); return Error(ex.Message);
} }
catch (InvalidOperationException ex) catch (InvalidOperationException ex)
{ {
_logger.LogFailure(action, correlationId, "invalid_operation", ex.Message); CommandLogger.LogFailure(action, correlationId, "invalid_operation", ex.Message);
return Error(ex.Message); return Error(ex.Message);
} }
catch (FileNotFoundException ex) catch (FileNotFoundException ex)
{ {
_logger.LogFailure(action, correlationId, "not_found", ex.Message); CommandLogger.LogFailure(action, correlationId, "not_found", ex.Message);
return Error(ex.Message); return Error(ex.Message);
} }
catch catch
{ {
_logger.LogFailure(action, correlationId, "internal_error"); CommandLogger.LogFailure(action, correlationId, "internal_error");
return Error("Internal error"); return Error("Internal error");
} }
_logger.LogSuccess(action, correlationId); CommandLogger.LogSuccess(action, correlationId);
return JsonSerializer.Serialize(new { ok = true, data = result }); return JsonSerializer.Serialize(new { ok = true, data = result });
} }

View File

@ -7,9 +7,7 @@ public sealed class DiskEntryFileRepository : IEntryFileRepository
if (!Directory.Exists(dataDirectory)) if (!Directory.Exists(dataDirectory))
return []; return [];
return Directory.GetFiles(dataDirectory, "*.md") return [.. Directory.GetFiles(dataDirectory, "*.md").OrderBy(Path.GetFileName, StringComparer.Ordinal)];
.OrderBy(Path.GetFileName, StringComparer.Ordinal)
.ToArray();
} }
public string ReadFile(string filePath) => File.ReadAllText(filePath); public string ReadFile(string filePath) => File.ReadAllText(filePath);

View File

@ -183,7 +183,7 @@ public class FileFragmentRepository : IFragmentRepository
return []; return [];
var docs = JsonSerializer.Deserialize<List<FragmentDocument>>(json, _jsonOptions) ?? []; var docs = JsonSerializer.Deserialize<List<FragmentDocument>>(json, _jsonOptions) ?? [];
return docs.Select(d => new Fragment(d.Id, d.Type, d.Description, d.Time, d.Tags)).ToList(); return [.. docs.Select(d => new Fragment(d.Id, d.Type, d.Description, d.Time, d.Tags))];
} }
private void SaveStoreLocked() private void SaveStoreLocked()

View File

@ -5,18 +5,18 @@ namespace Journal.Core.Services;
public sealed class CommandLogger public sealed class CommandLogger
{ {
public void LogStart(string action, string correlationId, JsonElement? payload) public static void LogStart(string action, string correlationId, JsonElement? payload)
{ {
var redactedPayload = LogRedactor.RedactPayload(payload); var redactedPayload = LogRedactor.RedactPayload(payload);
EmitLog("information", action, correlationId, "start", redactedPayload); EmitLog("information", action, correlationId, "start", redactedPayload);
} }
public void LogSuccess(string action, string correlationId) public static void LogSuccess(string action, string correlationId)
{ {
EmitLog("information", action, correlationId, "success"); EmitLog("information", action, correlationId, "success");
} }
public void LogFailure(string action, string correlationId, string errorType, string? message = null) public static void LogFailure(string action, string correlationId, string errorType, string? message = null)
{ {
var details = string.IsNullOrWhiteSpace(message) var details = string.IsNullOrWhiteSpace(message)
? "" ? ""

View File

@ -2,18 +2,11 @@ using Journal.Core.Dtos;
namespace Journal.Core.Services; namespace Journal.Core.Services;
public sealed class DisabledAiService : IAiService public sealed class DisabledAiService(string provider, string message = "AI provider disabled.", bool healthy = true) : IAiService
{ {
private readonly string _provider; private readonly string _provider = string.IsNullOrWhiteSpace(provider) ? "none" : provider.Trim();
private readonly string _message; private readonly string _message = string.IsNullOrWhiteSpace(message) ? "AI provider disabled." : message.Trim();
private readonly bool _healthy; private readonly bool _healthy = healthy;
public DisabledAiService(string provider, string message = "AI provider disabled.", bool healthy = true)
{
_provider = string.IsNullOrWhiteSpace(provider) ? "none" : provider.Trim();
_message = string.IsNullOrWhiteSpace(message) ? "AI provider disabled." : message.Trim();
_healthy = healthy;
}
public Task<AiHealthDto> HealthAsync(CancellationToken cancellationToken = default) => public Task<AiHealthDto> HealthAsync(CancellationToken cancellationToken = default) =>
Task.FromResult(new AiHealthDto(_provider, Enabled: false, Healthy: _healthy, Message: _message)); Task.FromResult(new AiHealthDto(_provider, Enabled: false, Healthy: _healthy, Message: _message));

View File

@ -2,16 +2,10 @@ using Journal.Core.Dtos;
namespace Journal.Core.Services; namespace Journal.Core.Services;
public sealed class DisabledSpeechBridgeService : ISpeechBridgeService public sealed class DisabledSpeechBridgeService(string provider = "none", string message = "Speech bridge is disabled.") : ISpeechBridgeService
{ {
private readonly string _provider; private readonly string _provider = string.IsNullOrWhiteSpace(provider) ? "none" : provider.Trim();
private readonly string _message; private readonly string _message = string.IsNullOrWhiteSpace(message) ? "Speech bridge is disabled." : message.Trim();
public DisabledSpeechBridgeService(string provider = "none", string message = "Speech bridge is disabled.")
{
_provider = string.IsNullOrWhiteSpace(provider) ? "none" : provider.Trim();
_message = string.IsNullOrWhiteSpace(message) ? "Speech bridge is disabled." : message.Trim();
}
public Task<SpeechDevicesResultDto> ListDevicesAsync(CancellationToken cancellationToken = default) public Task<SpeechDevicesResultDto> ListDevicesAsync(CancellationToken cancellationToken = default)
{ {

View File

@ -3,20 +3,16 @@ using Journal.Core.Repositories;
namespace Journal.Core.Services; namespace Journal.Core.Services;
public sealed class EntryFileService : IEntryFileService public sealed class EntryFileService(IEntryFileRepository repo) : IEntryFileService
{ {
private readonly IEntryFileRepository _repo; private readonly IEntryFileRepository _repo = repo ?? throw new ArgumentNullException(nameof(repo));
public EntryFileService(IEntryFileRepository repo) =>
_repo = repo ?? throw new ArgumentNullException(nameof(repo));
public IReadOnlyList<EntryListItem> ListEntries(string dataDirectory) public IReadOnlyList<EntryListItem> ListEntries(string dataDirectory)
{ {
return _repo.ListMarkdownFiles(dataDirectory) return [.. _repo.ListMarkdownFiles(dataDirectory)
.Select(path => new EntryListItem( .Select(path => new EntryListItem(
FileName: _repo.GetFileName(path), FileName: _repo.GetFileName(path),
FilePath: _repo.GetFullPath(path))) FilePath: _repo.GetFullPath(path)))];
.ToArray();
} }
public EntryLoadResult LoadEntry(string filePath) public EntryLoadResult LoadEntry(string filePath)

View File

@ -5,11 +5,9 @@ using Journal.Core.Repositories;
namespace Journal.Core.Services; namespace Journal.Core.Services;
public class FragmentService : IFragmentService public class FragmentService(IFragmentRepository repo) : IFragmentService
{ {
private readonly IFragmentRepository _repo; private readonly IFragmentRepository _repo = repo ?? throw new ArgumentNullException(nameof(repo));
public FragmentService(IFragmentRepository repo) => _repo = repo ?? throw new ArgumentNullException(nameof(repo));
private static FragmentDto Map(Fragment f) => new( private static FragmentDto Map(Fragment f) => new(
f.Id, f.Id,

View File

@ -5,7 +5,7 @@ using Microsoft.Data.Sqlite;
namespace Journal.Core.Services; namespace Journal.Core.Services;
public sealed class JournalDatabaseService : IJournalDatabaseService public sealed class JournalDatabaseService(IJournalConfigService config) : IJournalDatabaseService
{ {
public const int KeySize = 32; public const int KeySize = 32;
public const int Iterations = 600_000; public const int Iterations = 600_000;
@ -15,12 +15,7 @@ public sealed class JournalDatabaseService : IJournalDatabaseService
private static readonly IReadOnlyList<string> RequiredSchemaTables = private static readonly IReadOnlyList<string> RequiredSchemaTables =
["entries", "sections", "fragments", "tags", "fragment_tags"]; ["entries", "sections", "fragments", "tags", "fragment_tags"];
private readonly IJournalConfigService _config; private readonly IJournalConfigService _config = config;
public JournalDatabaseService(IJournalConfigService config)
{
_config = config;
}
public string GetDatabasePath(string? dataDirectory = null) public string GetDatabasePath(string? dataDirectory = null)
{ {

View File

@ -4,19 +4,14 @@ using Journal.Core.Models;
namespace Journal.Core.Services; namespace Journal.Core.Services;
public sealed class PythonSidecarClient public sealed class PythonSidecarClient(JournalConfig config)
{ {
private static readonly JsonSerializerOptions JsonOptions = new() private static readonly JsonSerializerOptions JsonOptions = new()
{ {
PropertyNameCaseInsensitive = true PropertyNameCaseInsensitive = true
}; };
private readonly JournalConfig _config; private readonly JournalConfig _config = config;
public PythonSidecarClient(JournalConfig config)
{
_config = config;
}
public async Task<JsonElement?> SendAsync(string action, object payload, CancellationToken cancellationToken) public async Task<JsonElement?> SendAsync(string action, object payload, CancellationToken cancellationToken)
{ {

View File

@ -3,18 +3,11 @@ using Journal.Core.Dtos;
namespace Journal.Core.Services; namespace Journal.Core.Services;
public sealed class SidecarCli public sealed class SidecarCli(IVaultStorageService vaultStorage, IEntrySearchService entrySearch, IJournalConfigService config)
{ {
private readonly IVaultStorageService _vaultStorage; private readonly IVaultStorageService _vaultStorage = vaultStorage;
private readonly IEntrySearchService _entrySearch; private readonly IEntrySearchService _entrySearch = entrySearch;
private readonly IJournalConfigService _config; private readonly IJournalConfigService _config = config;
public SidecarCli(IVaultStorageService vaultStorage, IEntrySearchService entrySearch, IJournalConfigService config)
{
_vaultStorage = vaultStorage;
_entrySearch = entrySearch;
_config = config;
}
public async Task<int> RunAsync(string[] args, Entry entry) public async Task<int> RunAsync(string[] args, Entry entry)
{ {
@ -34,9 +27,9 @@ public sealed class SidecarCli
} }
if (string.Equals(args[0], "vault", StringComparison.OrdinalIgnoreCase)) if (string.Equals(args[0], "vault", StringComparison.OrdinalIgnoreCase))
return RunVaultCommand(args.Skip(1).ToArray()); return RunVaultCommand([.. args.Skip(1)]);
if (string.Equals(args[0], "search", StringComparison.OrdinalIgnoreCase)) if (string.Equals(args[0], "search", StringComparison.OrdinalIgnoreCase))
return RunSearchCommand(args.Skip(1).ToArray()); return RunSearchCommand([.. args.Skip(1)]);
Console.Error.WriteLine($"Unknown command: {args[0]}"); Console.Error.WriteLine($"Unknown command: {args[0]}");
PrintUsage(); PrintUsage();
@ -60,7 +53,7 @@ public sealed class SidecarCli
return 2; return 2;
} }
if (!TryParseVaultOptions(args.Skip(1).ToArray(), out var options, out var parseError)) if (!TryParseVaultOptions([.. args.Skip(1)], out var options, out var parseError))
{ {
Console.Error.WriteLine(parseError); Console.Error.WriteLine(parseError);
PrintVaultUsage(); PrintVaultUsage();

View File

@ -6,14 +6,12 @@ using System.Threading;
namespace Journal.Core.Services; namespace Journal.Core.Services;
public class VaultStorageService : IVaultStorageService public class VaultStorageService(IVaultCryptoService crypto) : IVaultStorageService
{ {
private readonly IVaultCryptoService _crypto; private readonly IVaultCryptoService _crypto = crypto;
private readonly Dictionary<string, string> _monthFingerprintCache = new(StringComparer.Ordinal); private readonly Dictionary<string, string> _monthFingerprintCache = new(StringComparer.Ordinal);
private readonly object _vaultIoLock = new(); private readonly object _vaultIoLock = new();
public VaultStorageService(IVaultCryptoService crypto) => _crypto = crypto;
public string GetMonthlyVaultFileName(DateTime date) => date.ToString("yyyy-MM") + ".vault"; public string GetMonthlyVaultFileName(DateTime date) => date.ToString("yyyy-MM") + ".vault";
public bool LoadAllVaults(string password, string vaultDirectory, string dataDirectory) public bool LoadAllVaults(string password, string vaultDirectory, string dataDirectory)

View File

@ -2,4 +2,5 @@
<Project Path="Journal.Api/Journal.Api.csproj" /> <Project Path="Journal.Api/Journal.Api.csproj" />
<Project Path="Journal.Core/Journal.Core.csproj" /> <Project Path="Journal.Core/Journal.Core.csproj" />
<Project Path="Journal.Sidecar/Journal.Sidecar.csproj" /> <Project Path="Journal.Sidecar/Journal.Sidecar.csproj" />
<Project Path="Journal.SmokeTests/Journal.SmokeTests.csproj" />
</Solution> </Solution>