From f86ac5e4b937551368b9edf36d7488691987c8a7 Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Mon, 23 Feb 2026 22:10:36 -0600 Subject: [PATCH] Remove dead code, fix duplicated connection logic, eliminate fake async - Delete unused FileFragmentRepository.cs - DatabaseSessionService delegates to JournalDatabaseService for OpenEncryptedConnection/EnsureSchema instead of duplicating logic - Make IJournalDatabaseService expose OpenEncryptedConnection and EnsureSchema as public - Convert entire fragment chain from fake async (Task.FromResult wrappers) to synchronous: IFragmentRepository, SqliteFragmentRepository, InMemoryFragmentRepository, IFragmentService, FragmentService, Entry.cs dispatch, and SmokeTests Co-Authored-By: Warp --- Journal.Core/Entry.cs | 12 +- .../Repositories/FileFragmentRepository.cs | 228 ------------------ .../Repositories/IFragmentRepository.cs | 16 +- .../InMemoryFragmentRepository.cs | 39 ++- .../Repositories/SqliteFragmentRepository.cs | 42 ++-- .../Database/DatabaseSessionService.cs | 37 +-- .../Database/IJournalDatabaseService.cs | 3 + .../Database/JournalDatabaseService.cs | 8 +- .../Services/Fragments/FragmentService.cs | 30 +-- .../Services/Fragments/IFragmentService.cs | 16 +- Journal.SmokeTests/Program.cs | 30 ++- 11 files changed, 100 insertions(+), 361 deletions(-) delete mode 100644 Journal.Core/Repositories/FileFragmentRepository.cs diff --git a/Journal.Core/Entry.cs b/Journal.Core/Entry.cs index 058e0be..860a0ae 100644 --- a/Journal.Core/Entry.cs +++ b/Journal.Core/Entry.cs @@ -81,18 +81,18 @@ public class Entry( switch (action) { case "fragments.list": - result = await _fragments.GetAllAsync(); + result = _fragments.GetAll(); break; case "fragments.get": if (!Guid.TryParse(cmd.Id, out var getId)) return Error("Invalid or missing id"); - result = await _fragments.GetByIdAsync(getId); + result = _fragments.GetById(getId); break; case "fragments.create": var createDto = DeserializePayload(cmd.Payload); if (createDto is null) return Error("Missing or invalid payload"); - result = await _fragments.CreateAsync(createDto); + result = _fragments.Create(createDto); break; case "fragments.update": if (!Guid.TryParse(cmd.Id, out var updateId)) @@ -100,15 +100,15 @@ public class Entry( var updateDto = DeserializePayload(cmd.Payload); if (updateDto is null) return Error("Missing or invalid payload"); - result = await _fragments.UpdateAsync(updateId, updateDto); + result = _fragments.Update(updateId, updateDto); break; case "fragments.delete": if (!Guid.TryParse(cmd.Id, out var deleteId)) return Error("Invalid or missing id"); - result = await _fragments.RemoveAsync(deleteId); + result = _fragments.Remove(deleteId); break; case "fragments.search": - result = await _fragments.SearchAsync(cmd.Type, cmd.Tag); + result = _fragments.Search(cmd.Type, cmd.Tag); break; case "search.entries": var searchPayload = DeserializePayload(cmd.Payload); diff --git a/Journal.Core/Repositories/FileFragmentRepository.cs b/Journal.Core/Repositories/FileFragmentRepository.cs deleted file mode 100644 index 89ace7e..0000000 --- a/Journal.Core/Repositories/FileFragmentRepository.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System.Text.Json; -using Journal.Core.Models; - -namespace Journal.Core.Repositories; - -public class FileFragmentRepository : IFragmentRepository -{ - private readonly Lock _lock = new(); - private readonly string _storagePath; - private readonly JsonSerializerOptions _jsonOptions = new() - { - WriteIndented = true - }; - private readonly List _store; - - public FileFragmentRepository() : this(storagePath: null) - { - } - - public FileFragmentRepository(string? storagePath) - { - _storagePath = ResolveStoragePath(storagePath); - _store = LoadStore(_storagePath); - } - - public Task> GetAllAsync() - { - lock (_lock) - { - return Task.FromResult(_store.ToList()); - } - } - - public Task GetByIdAsync(Guid id) - { - lock (_lock) - { - return Task.FromResult(_store.FirstOrDefault(f => f.Id == id)); - } - } - - public Task AddAsync(Fragment fragment) - { - ArgumentNullException.ThrowIfNull(fragment); - lock (_lock) - { - Normalize(fragment); - _store.Add(fragment); - SaveStoreLocked(); - } - return Task.CompletedTask; - } - - public Task RemoveAsync(Guid id) - { - lock (_lock) - { - var item = _store.FirstOrDefault(f => f.Id == id); - if (item is null) - return Task.FromResult(false); - - var removed = _store.Remove(item); - if (removed) - SaveStoreLocked(); - return Task.FromResult(removed); - } - } - - public Task UpdateAsync( - Guid id, - string? type = null, - string? description = null, - IEnumerable? tags = null, - DateTimeOffset? time = null) - { - lock (_lock) - { - var item = _store.FirstOrDefault(f => f.Id == id); - if (item is null) - return Task.FromResult(false); - - if (type != null) - { - if (string.IsNullOrWhiteSpace(type)) - throw new ArgumentException("Type cannot be empty", nameof(type)); - item.Type = type.Trim(); - } - - if (description != null) - { - if (string.IsNullOrWhiteSpace(description)) - throw new ArgumentException("Description cannot be empty", nameof(description)); - item.Description = description.Trim(); - } - - if (tags != null) - { - item.Tags = [.. - tags.Where(t => !string.IsNullOrWhiteSpace(t)) - .Select(t => t.Trim())]; - } - - if (time.HasValue) - item.Time = time.Value; - - SaveStoreLocked(); - return Task.FromResult(true); - } - } - - public Task> GetByTagAsync(string tag) - { - var q = tag?.Trim(); - if (string.IsNullOrWhiteSpace(q)) - return Task.FromResult(new List()); - - lock (_lock) - { - var items = _store - .Where(f => f.Tags.Any(t => t.Contains(q, StringComparison.OrdinalIgnoreCase))) - .ToList(); - return Task.FromResult(items); - } - } - - public Task> GetByTypeAsync(string type) - { - var q = type?.Trim(); - if (string.IsNullOrWhiteSpace(q)) - return Task.FromResult(new List()); - - lock (_lock) - { - var items = _store - .Where(f => f.Type.Contains(q, StringComparison.OrdinalIgnoreCase)) - .ToList(); - return Task.FromResult(items); - } - } - - public Task> SearchAsync(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null) - { - var qType = type?.Trim(); - var qTag = tag?.Trim(); - - lock (_lock) - { - IEnumerable results = _store; - - if (!string.IsNullOrWhiteSpace(qType)) - results = results.Where(f => f.Type.Contains(qType, StringComparison.OrdinalIgnoreCase)); - if (!string.IsNullOrWhiteSpace(qTag)) - results = results.Where(f => f.Tags.Any(t => t.Contains(qTag, StringComparison.OrdinalIgnoreCase))); - if (timeAfter.HasValue) - results = results.Where(f => f.Time > timeAfter.Value); - - return Task.FromResult(results.ToList()); - } - } - - private static string ResolveStoragePath(string? storagePath) - { - var configured = storagePath; - if (string.IsNullOrWhiteSpace(configured)) - configured = Environment.GetEnvironmentVariable("JOURNAL_FRAGMENT_STORE_PATH"); - if (string.IsNullOrWhiteSpace(configured)) - configured = Path.Combine(Environment.CurrentDirectory, ".journal-sidecar", "fragments.json"); - - return Path.GetFullPath(configured); - } - - private List LoadStore(string path) - { - var directory = Path.GetDirectoryName(path); - if (!string.IsNullOrWhiteSpace(directory)) - Directory.CreateDirectory(directory); - - if (!File.Exists(path)) - return []; - - var json = File.ReadAllText(path); - if (string.IsNullOrWhiteSpace(json)) - return []; - - var docs = JsonSerializer.Deserialize>(json, _jsonOptions) ?? []; - return [.. docs.Select(d => new Fragment(d.Id, d.Type, d.Description, d.Time, d.Tags))]; - } - - private void SaveStoreLocked() - { - var directory = Path.GetDirectoryName(_storagePath); - if (!string.IsNullOrWhiteSpace(directory)) - Directory.CreateDirectory(directory); - - var docs = _store.Select(f => new FragmentDocument - { - Id = f.Id, - Type = f.Type, - Description = f.Description, - Time = f.Time, - Tags = [.. f.Tags] - }).ToList(); - var json = JsonSerializer.Serialize(docs, _jsonOptions); - - var tempPath = _storagePath + ".tmp"; - File.WriteAllText(tempPath, json); - File.Copy(tempPath, _storagePath, overwrite: true); - File.Delete(tempPath); - } - - private static void Normalize(Fragment fragment) - { - fragment.Type = fragment.Type.Trim(); - fragment.Description = fragment.Description.Trim(); - fragment.Tags = [.. - fragment.Tags.Where(t => !string.IsNullOrWhiteSpace(t)) - .Select(t => t.Trim())]; - } - - private sealed class FragmentDocument - { - public Guid Id { get; init; } - public string Type { get; init; } = ""; - public string Description { get; init; } = ""; - public DateTimeOffset Time { get; init; } - public List Tags { get; init; } = []; - } -} diff --git a/Journal.Core/Repositories/IFragmentRepository.cs b/Journal.Core/Repositories/IFragmentRepository.cs index 54011c1..bb05f0d 100644 --- a/Journal.Core/Repositories/IFragmentRepository.cs +++ b/Journal.Core/Repositories/IFragmentRepository.cs @@ -4,12 +4,12 @@ namespace Journal.Core.Repositories; public interface IFragmentRepository { - Task> GetAllAsync(); - Task GetByIdAsync(Guid id); - Task AddAsync(Fragment fragment); - Task RemoveAsync(Guid id); - Task UpdateAsync(Guid id, string? type = null, string? description = null, IEnumerable? tags = null, DateTimeOffset? time = null); - Task> GetByTagAsync(string tag); - Task> GetByTypeAsync(string type); - Task> SearchAsync(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null); + List GetAll(); + Fragment? GetById(Guid id); + void Add(Fragment fragment); + bool Remove(Guid id); + bool Update(Guid id, string? type = null, string? description = null, IEnumerable? tags = null, DateTimeOffset? time = null); + List GetByTag(string tag); + List GetByType(string type); + List Search(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null); } diff --git a/Journal.Core/Repositories/InMemoryFragmentRepository.cs b/Journal.Core/Repositories/InMemoryFragmentRepository.cs index d283645..b9f6cf5 100644 --- a/Journal.Core/Repositories/InMemoryFragmentRepository.cs +++ b/Journal.Core/Repositories/InMemoryFragmentRepository.cs @@ -7,23 +7,23 @@ public class InMemoryFragmentRepository : IFragmentRepository private readonly List _store = []; private readonly Lock _lock = new(); - public Task> GetAllAsync() + public List GetAll() { lock (_lock) { - return Task.FromResult(_store.ToList()); + return _store.ToList(); } } - public Task GetByIdAsync(Guid id) + public Fragment? GetById(Guid id) { lock (_lock) { - return Task.FromResult(_store.FirstOrDefault(f => f.Id == id)); + return _store.FirstOrDefault(f => f.Id == id); } } - public Task AddAsync(Fragment fragment) + public void Add(Fragment fragment) { if (fragment is null) throw new ArgumentNullException(nameof(fragment)); lock (_lock) @@ -39,25 +39,24 @@ public class InMemoryFragmentRepository : IFragmentRepository _store.Add(fragment); } - return Task.CompletedTask; } - public Task RemoveAsync(Guid id) + public bool Remove(Guid id) { lock (_lock) { var item = _store.FirstOrDefault(f => f.Id == id); - if (item is null) return Task.FromResult(false); - return Task.FromResult(_store.Remove(item)); + if (item is null) return false; + return _store.Remove(item); } } - public Task UpdateAsync(Guid id, string? type = null, string? description = null, IEnumerable? tags = null, DateTimeOffset? time = null) + public bool Update(Guid id, string? type = null, string? description = null, IEnumerable? tags = null, DateTimeOffset? time = null) { lock (_lock) { var item = _store.FirstOrDefault(f => f.Id == id); - if (item is null) return Task.FromResult(false); + if (item is null) return false; if (type != null) { @@ -81,31 +80,31 @@ public class InMemoryFragmentRepository : IFragmentRepository if (time.HasValue) item.Time = time.Value; - return Task.FromResult(true); + return true; } } - public Task> GetByTagAsync(string tag) + public List GetByTag(string tag) { var q = tag?.Trim(); - if (string.IsNullOrWhiteSpace(q)) return Task.FromResult(new List()); + if (string.IsNullOrWhiteSpace(q)) return []; lock (_lock) { - return Task.FromResult(_store.Where(f => f.Tags?.Any(t => !string.IsNullOrWhiteSpace(t) && t.Contains(q, StringComparison.OrdinalIgnoreCase)) == true).ToList()); + return _store.Where(f => f.Tags?.Any(t => !string.IsNullOrWhiteSpace(t) && t.Contains(q, StringComparison.OrdinalIgnoreCase)) == true).ToList(); } } - public Task> GetByTypeAsync(string type) + public List GetByType(string type) { var q = type?.Trim(); - if (string.IsNullOrWhiteSpace(q)) return Task.FromResult(new List()); + if (string.IsNullOrWhiteSpace(q)) return []; lock (_lock) { - return Task.FromResult(_store.Where(f => !string.IsNullOrWhiteSpace(f.Type) && f.Type.Trim().Contains(q, StringComparison.OrdinalIgnoreCase)).ToList()); + return _store.Where(f => !string.IsNullOrWhiteSpace(f.Type) && f.Type.Trim().Contains(q, StringComparison.OrdinalIgnoreCase)).ToList(); } } - public Task> SearchAsync(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null) + public List Search(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null) { var results = _store.AsEnumerable(); var qType = type?.Trim(); @@ -120,7 +119,7 @@ public class InMemoryFragmentRepository : IFragmentRepository if (timeAfter.HasValue) results = results.Where(f => f.Time > timeAfter.Value); - return Task.FromResult(results.ToList()); + return results.ToList(); } } } diff --git a/Journal.Core/Repositories/SqliteFragmentRepository.cs b/Journal.Core/Repositories/SqliteFragmentRepository.cs index d69a52f..50c24a7 100644 --- a/Journal.Core/Repositories/SqliteFragmentRepository.cs +++ b/Journal.Core/Repositories/SqliteFragmentRepository.cs @@ -8,37 +8,33 @@ public sealed class SqliteFragmentRepository(IDatabaseSessionService session) : { private readonly IDatabaseSessionService _session = session; - public Task> GetAllAsync() + public List GetAll() { var conn = _session.GetConnection(); - var fragments = ReadAllFragments(conn); - return Task.FromResult(fragments); + return ReadAllFragments(conn); } - public Task GetByIdAsync(Guid id) + public Fragment? GetById(Guid id) { var conn = _session.GetConnection(); - var fragment = ReadFragment(conn, id); - return Task.FromResult(fragment); + return ReadFragment(conn, id); } - public Task AddAsync(Fragment fragment) + public void Add(Fragment fragment) { ArgumentNullException.ThrowIfNull(fragment); Normalize(fragment); var conn = _session.GetConnection(); InsertFragment(conn, fragment); - return Task.CompletedTask; } - public Task RemoveAsync(Guid id) + public bool Remove(Guid id) { var conn = _session.GetConnection(); - var deleted = DeleteFragment(conn, id); - return Task.FromResult(deleted); + return DeleteFragment(conn, id); } - public Task UpdateAsync( + public bool Update( Guid id, string? type = null, string? description = null, @@ -48,7 +44,7 @@ public sealed class SqliteFragmentRepository(IDatabaseSessionService session) : var conn = _session.GetConnection(); var existing = ReadFragment(conn, id); if (existing is null) - return Task.FromResult(false); + return false; if (type != null) { @@ -75,38 +71,36 @@ public sealed class SqliteFragmentRepository(IDatabaseSessionService session) : existing.Time = time.Value; UpdateFragmentRow(conn, existing); - return Task.FromResult(true); + return true; } - public Task> GetByTagAsync(string tag) + public List GetByTag(string tag) { var q = tag?.Trim(); if (string.IsNullOrWhiteSpace(q)) - return Task.FromResult(new List()); + return []; var conn = _session.GetConnection(); var all = ReadAllFragments(conn); - var items = all + return all .Where(f => f.Tags.Any(t => t.Contains(q, StringComparison.OrdinalIgnoreCase))) .ToList(); - return Task.FromResult(items); } - public Task> GetByTypeAsync(string type) + public List GetByType(string type) { var q = type?.Trim(); if (string.IsNullOrWhiteSpace(q)) - return Task.FromResult(new List()); + return []; var conn = _session.GetConnection(); var all = ReadAllFragments(conn); - var items = all + return all .Where(f => f.Type.Contains(q, StringComparison.OrdinalIgnoreCase)) .ToList(); - return Task.FromResult(items); } - public Task> SearchAsync(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null) + public List Search(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null) { var conn = _session.GetConnection(); IEnumerable results = ReadAllFragments(conn); @@ -121,7 +115,7 @@ public sealed class SqliteFragmentRepository(IDatabaseSessionService session) : if (timeAfter.HasValue) results = results.Where(f => f.Time > timeAfter.Value); - return Task.FromResult(results.ToList()); + return results.ToList(); } // ── Private helpers ────────────────────────────────────────────── diff --git a/Journal.Core/Services/Database/DatabaseSessionService.cs b/Journal.Core/Services/Database/DatabaseSessionService.cs index 086bcd1..f16f7c6 100644 --- a/Journal.Core/Services/Database/DatabaseSessionService.cs +++ b/Journal.Core/Services/Database/DatabaseSessionService.cs @@ -49,8 +49,8 @@ public sealed class DatabaseSessionService(IJournalDatabaseService database) : I if (_connection is not null) return _connection; - _connection = OpenEncryptedConnection(_password, _dataDirectory); - EnsureSchema(_connection, _database); + _connection = _database.OpenEncryptedConnection(_password, _dataDirectory); + _database.EnsureSchema(_connection); return _connection; } } @@ -63,37 +63,4 @@ public sealed class DatabaseSessionService(IJournalDatabaseService database) : I _connection = null; } } - - private SqliteConnection OpenEncryptedConnection(string password, string? dataDirectory) - { - var dbPath = _database.GetDatabasePath(dataDirectory); - var directory = Path.GetDirectoryName(dbPath); - if (!string.IsNullOrWhiteSpace(directory)) - Directory.CreateDirectory(directory); - - var connection = new SqliteConnection( - $"Data Source={dbPath};Mode=ReadWriteCreate;Pooling=False"); - connection.Open(); - - using var keyCmd = connection.CreateCommand(); - keyCmd.CommandText = _database.BuildPragmaKeyStatement(password) + ";"; - keyCmd.ExecuteNonQuery(); - - // Verify the key is correct - using var verifyCmd = connection.CreateCommand(); - verifyCmd.CommandText = "SELECT count(*) FROM sqlite_master;"; - _ = verifyCmd.ExecuteScalar(); - - return connection; - } - - private static void EnsureSchema(SqliteConnection connection, IJournalDatabaseService database) - { - foreach (var statement in database.GetSchemaStatements().Values) - { - using var cmd = connection.CreateCommand(); - cmd.CommandText = statement; - cmd.ExecuteNonQuery(); - } - } } diff --git a/Journal.Core/Services/Database/IJournalDatabaseService.cs b/Journal.Core/Services/Database/IJournalDatabaseService.cs index ab23abb..8d240fd 100644 --- a/Journal.Core/Services/Database/IJournalDatabaseService.cs +++ b/Journal.Core/Services/Database/IJournalDatabaseService.cs @@ -1,4 +1,5 @@ using Journal.Core.Dtos; +using Microsoft.Data.Sqlite; namespace Journal.Core.Services.Database; @@ -8,6 +9,8 @@ public interface IJournalDatabaseService byte[] DeriveDatabaseKey(string password); string BuildPragmaKeyStatement(string password); IReadOnlyDictionary GetSchemaStatements(); + SqliteConnection OpenEncryptedConnection(string password, string? dataDirectory = null); + void EnsureSchema(SqliteConnection connection); string WriteSchemaBootstrap(string? dataDirectory = null); JournalDatabaseStatus GetStatus(string password, string? dataDirectory = null); JournalDatabaseHydrationResult HydrateWorkspace(string password, string? dataDirectory = null); diff --git a/Journal.Core/Services/Database/JournalDatabaseService.cs b/Journal.Core/Services/Database/JournalDatabaseService.cs index 1b5bfcc..301dcac 100644 --- a/Journal.Core/Services/Database/JournalDatabaseService.cs +++ b/Journal.Core/Services/Database/JournalDatabaseService.cs @@ -135,7 +135,7 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour Directory.CreateDirectory(directory); using var connection = OpenEncryptedConnection(password, directory); - CreateSchema(connection); + EnsureSchema(connection); var runtimeReady = HasRequiredTables(connection); var entryFilesProcessed = Directory.GetFiles(directory, "*.md", SearchOption.TopDirectoryOnly).Length; @@ -166,7 +166,7 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour } } - private SqliteConnection OpenEncryptedConnection(string password, string? dataDirectory = null) + public SqliteConnection OpenEncryptedConnection(string password, string? dataDirectory = null) { if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Password cannot be empty.", nameof(password)); @@ -187,7 +187,7 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour return connection; } - private void CreateSchema(SqliteConnection connection) + public void EnsureSchema(SqliteConnection connection) { foreach (var statement in GetSchemaStatements().Values) { @@ -217,7 +217,7 @@ public sealed class JournalDatabaseService(IJournalConfigService config) : IJour try { using var connection = OpenEncryptedConnection(password, dataDirectory); - CreateSchema(connection); + EnsureSchema(connection); var ready = HasRequiredTables(connection); return ready ? (true, "SQLCipher runtime is available and schema tables are present.") diff --git a/Journal.Core/Services/Fragments/FragmentService.cs b/Journal.Core/Services/Fragments/FragmentService.cs index f5e8035..55917fa 100644 --- a/Journal.Core/Services/Fragments/FragmentService.cs +++ b/Journal.Core/Services/Fragments/FragmentService.cs @@ -17,7 +17,7 @@ public class FragmentService(IFragmentRepository repo) : IFragmentService f.Tags != null ? [.. f.Tags] : [] ); - public async Task CreateAsync(CreateFragmentDto dto) + public FragmentDto Create(CreateFragmentDto dto) { ArgumentNullException.ThrowIfNull(dto); @@ -28,11 +28,11 @@ public class FragmentService(IFragmentRepository repo) : IFragmentService if (dto.Tags != null) f.Tags = [.. dto.Tags.Where(t => !string.IsNullOrWhiteSpace(t)).Select(t => t!.Trim())]; - await _repo.AddAsync(f); + _repo.Add(f); return Map(f); } - public async Task UpdateAsync(Guid id, UpdateFragmentDto dto) + public bool Update(Guid id, UpdateFragmentDto dto) { ArgumentNullException.ThrowIfNull(dto); if (dto.Type != null && string.IsNullOrWhiteSpace(dto.Type)) @@ -44,38 +44,38 @@ public class FragmentService(IFragmentRepository repo) : IFragmentService var description = dto.Description?.Trim(); var tags = dto.Tags?.Where(t => !string.IsNullOrWhiteSpace(t)).Select(t => t!.Trim()).ToList(); - return await _repo.UpdateAsync(id, type, description, tags, dto.Time); + return _repo.Update(id, type, description, tags, dto.Time); } - public Task RemoveAsync(Guid id) => _repo.RemoveAsync(id); + public bool Remove(Guid id) => _repo.Remove(id); - public async Task> SearchAsync(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null) + public List Search(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null) { - var items = await _repo.SearchAsync(type, tag, timeAfter); + var items = _repo.Search(type, tag, timeAfter); return [.. items.Select(Map)]; } - public async Task> GetByTagAsync(string tag) + public List GetByTag(string tag) { - var items = await _repo.GetByTagAsync(tag); + var items = _repo.GetByTag(tag); return [.. items.Select(Map)]; } - public async Task> GetByTypeAsync(string type) + public List GetByType(string type) { - var items = await _repo.GetByTypeAsync(type); + var items = _repo.GetByType(type); return [.. items.Select(Map)]; } - public async Task> GetAllAsync() + public List GetAll() { - var items = await _repo.GetAllAsync(); + var items = _repo.GetAll(); return [.. items.Select(Map)]; } - public async Task GetByIdAsync(Guid id) + public FragmentDto? GetById(Guid id) { - var f = await _repo.GetByIdAsync(id); + var f = _repo.GetById(id); return f is null ? null : Map(f); } } diff --git a/Journal.Core/Services/Fragments/IFragmentService.cs b/Journal.Core/Services/Fragments/IFragmentService.cs index d56252f..6087081 100644 --- a/Journal.Core/Services/Fragments/IFragmentService.cs +++ b/Journal.Core/Services/Fragments/IFragmentService.cs @@ -4,12 +4,12 @@ namespace Journal.Core.Services.Fragments; public interface IFragmentService { - Task CreateAsync(CreateFragmentDto dto); - Task UpdateAsync(Guid id, UpdateFragmentDto dto); - Task RemoveAsync(Guid id); - Task> SearchAsync(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null); - Task> GetByTagAsync(string tag); - Task> GetByTypeAsync(string type); - Task> GetAllAsync(); - Task GetByIdAsync(Guid id); + FragmentDto Create(CreateFragmentDto dto); + bool Update(Guid id, UpdateFragmentDto dto); + bool Remove(Guid id); + List Search(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null); + List GetByTag(string tag); + List GetByType(string type); + List GetAll(); + FragmentDto? GetById(Guid id); } diff --git a/Journal.SmokeTests/Program.cs b/Journal.SmokeTests/Program.cs index 4d6cfbd..2360538 100644 --- a/Journal.SmokeTests/Program.cs +++ b/Journal.SmokeTests/Program.cs @@ -133,49 +133,51 @@ static Entry NewEntry() static IJournalDatabaseService NewDatabaseService() => new JournalDatabaseService(new JournalConfigService()); -static async Task TestCreateTrimsAsync() +static Task TestCreateTrimsAsync() { var service = NewService(); - var created = await service.CreateAsync(new CreateFragmentDto(" !TRIGGER ", " stomach drop ", [" stress ", "", " body "])); + var created = service.Create(new CreateFragmentDto(" !TRIGGER ", " stomach drop ", [" stress ", "", " body "])); Assert(created.Type == "!TRIGGER", "Type should be trimmed."); Assert(created.Description == "stomach drop", "Description should be trimmed."); Assert(created.Tags.Count == 2, "Expected two normalized tags."); Assert(created.Tags[0] == "stress" && created.Tags[1] == "body", "Tags should be trimmed and preserved."); + return Task.CompletedTask; } -static async Task TestUpdateAcceptsTypeAsync() +static Task TestUpdateAcceptsTypeAsync() { var service = NewService(); - var created = await service.CreateAsync(new CreateFragmentDto("!TRIGGER", "one")); - var ok = await service.UpdateAsync(created.Id, new UpdateFragmentDto(Type: " !FLASHBACK ", Description: " two ", Tags: [" memory "])); + var created = service.Create(new CreateFragmentDto("!TRIGGER", "one")); + var ok = service.Update(created.Id, new UpdateFragmentDto(Type: " !FLASHBACK ", Description: " two ", Tags: [" memory "])); Assert(ok, "Expected update to succeed."); - var updated = await service.GetByIdAsync(created.Id); + var updated = service.GetById(created.Id); Assert(updated is not null, "Updated fragment should exist."); Assert(updated!.Type == "!FLASHBACK", "Updated type should be trimmed and stored."); Assert(updated.Description == "two", "Updated description should be trimmed and stored."); Assert(updated.Tags.Count == 1 && updated.Tags[0] == "memory", "Updated tags should be normalized."); + return Task.CompletedTask; } -static async Task TestUpdateRejectsWhitespaceTypeAsync() +static Task TestUpdateRejectsWhitespaceTypeAsync() { var service = NewService(); - var created = await service.CreateAsync(new CreateFragmentDto("!TRIGGER", "desc")); + var created = service.Create(new CreateFragmentDto("!TRIGGER", "desc")); try { - _ = await service.UpdateAsync(created.Id, new UpdateFragmentDto(Type: " ")); + _ = service.Update(created.Id, new UpdateFragmentDto(Type: " ")); } catch (ValidationException) { - return; + return Task.CompletedTask; } throw new InvalidOperationException("Expected ValidationException for whitespace type update."); } -static async Task TestFileRepositoryPersistsAsync() +static Task TestFileRepositoryPersistsAsync() { var tempRoot = Path.Combine(Path.GetTempPath(), "journal-smoke", Guid.NewGuid().ToString("N")); var dataDir = Path.Combine(tempRoot, "data"); @@ -193,14 +195,14 @@ static async Task TestFileRepositoryPersistsAsync() session1.SetPassword(password, dataDir); var repo1 = new SqliteFragmentRepository(session1); var service1 = new FragmentService(repo1); - var created = await service1.CreateAsync(new CreateFragmentDto("!TRIGGER", "persist me", ["tag1"])); + var created = service1.Create(new CreateFragmentDto("!TRIGGER", "persist me", ["tag1"])); // Second session: verify persistence using var session2 = new DatabaseSessionService(dbService); session2.SetPassword(password, dataDir); var repo2 = new SqliteFragmentRepository(session2); var service2 = new FragmentService(repo2); - var loaded = await service2.GetByIdAsync(created.Id); + var loaded = service2.GetById(created.Id); Assert(loaded is not null, "Expected fragment to persist across repository instances."); Assert(loaded!.Description == "persist me", "Persisted fragment description mismatch."); @@ -211,6 +213,8 @@ static async Task TestFileRepositoryPersistsAsync() if (Directory.Exists(tempRoot)) Directory.Delete(tempRoot, recursive: true); } + + return Task.CompletedTask; } static Task TestJournalEntryModelAsync()