From e933d90e07f6acfe296fb4f6e27acd19b9b492e0 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Thu, 13 Jun 2024 16:30:48 -0600 Subject: [PATCH] allow creating parts of speech as CRDTs and setup a PoC of pre seeding them. --- backend/LcmCrdt.Tests/LexboxApiTests.cs | 7 ++- .../Changes/CreatePartOfSpeechChange.cs | 19 ++++++++ backend/LcmCrdt/LcmCrdtKernel.cs | 7 ++- backend/LcmCrdt/Objects/PartOfSpeech.cs | 46 +++++++++++++++++++ backend/LcmCrdt/ProjectsService.cs | 13 +++++- 5 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 backend/LcmCrdt/Changes/CreatePartOfSpeechChange.cs create mode 100644 backend/LcmCrdt/Objects/PartOfSpeech.cs diff --git a/backend/LcmCrdt.Tests/LexboxApiTests.cs b/backend/LcmCrdt.Tests/LexboxApiTests.cs index f05e246369..44a047244f 100644 --- a/backend/LcmCrdt.Tests/LexboxApiTests.cs +++ b/backend/LcmCrdt.Tests/LexboxApiTests.cs @@ -1,5 +1,6 @@ using Crdt; using Crdt.Db; +using LcmCrdt.Changes; using LcmCrdt.Tests.Mocks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -330,6 +331,8 @@ public async Task UpdateSense() [Fact] public async Task UpdateSensePartOfSpeech() { + var partOfSpeechId = Guid.NewGuid(); + await DataModel.AddChange(Guid.NewGuid(), new CreatePartOfSpeechChange(partOfSpeechId, new MultiString() { { "en", "Adverb" } })); var entry = await _api.CreateEntry(new Entry { LexemeForm = new MultiString @@ -358,8 +361,10 @@ public async Task UpdateSensePartOfSpeech() entry.Senses[0].Id, _api.CreateUpdateBuilder() .Set(e => e.PartOfSpeech, "updated") + .Set(e => e.PartOfSpeechId, partOfSpeechId) .Build()); updatedSense.PartOfSpeech.Should().Be("updated"); + updatedSense.PartOfSpeechId.Should().Be(partOfSpeechId); } [Fact] @@ -392,7 +397,7 @@ public async Task UpdateSenseSemanticDomain() var updatedSense = await _api.UpdateSense(entry.Id, entry.Senses[0].Id, _api.CreateUpdateBuilder() - .Set(e => e.SemanticDomains[0], new SemanticDomain() { Id = Guid.Empty, Code = "updated", Name = new MultiString() }) + .Add(e => e.SemanticDomains, new SemanticDomain() { Id = Guid.Empty, Code = "updated", Name = new MultiString() }) .Build()); updatedSense.SemanticDomains.Select(sd => sd.Code).Should().Contain("updated"); } diff --git a/backend/LcmCrdt/Changes/CreatePartOfSpeechChange.cs b/backend/LcmCrdt/Changes/CreatePartOfSpeechChange.cs new file mode 100644 index 0000000000..84cd48eda0 --- /dev/null +++ b/backend/LcmCrdt/Changes/CreatePartOfSpeechChange.cs @@ -0,0 +1,19 @@ +using Crdt; +using Crdt.Changes; +using Crdt.Entities; +using MiniLcm; +using PartOfSpeech = LcmCrdt.Objects.PartOfSpeech; + +namespace LcmCrdt.Changes; + +public class CreatePartOfSpeechChange(Guid entityId, MultiString name, bool predefined = false) + : CreateChange(entityId), ISelfNamedType +{ + public MultiString Name { get; } = name; + public bool Predefined { get; } = predefined; + + public override async ValueTask NewEntity(Commit commit, ChangeContext context) + { + return new PartOfSpeech { Id = EntityId, Name = Name, Predefined = Predefined }; + } +} diff --git a/backend/LcmCrdt/LcmCrdtKernel.cs b/backend/LcmCrdt/LcmCrdtKernel.cs index 43a659bcca..abba532bbd 100644 --- a/backend/LcmCrdt/LcmCrdtKernel.cs +++ b/backend/LcmCrdt/LcmCrdtKernel.cs @@ -14,6 +14,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using PartOfSpeech = LcmCrdt.Objects.PartOfSpeech; namespace LcmCrdt; @@ -107,19 +108,23 @@ private static void ConfigureCrdt(CrdtConfig config) .HasConversion(list => JsonSerializer.Serialize(list, (JsonSerializerOptions?)null), json => JsonSerializer.Deserialize(json, (JsonSerializerOptions?)null) ?? Array.Empty()); - }); + }).Add(); config.ChangeTypeListBuilder.Add>() .Add>() .Add>() .Add>() + .Add>() .Add>() .Add>() .Add>() .Add>() + .Add>() + .Add() .Add() .Add() .Add() + .Add() .Add(); } diff --git a/backend/LcmCrdt/Objects/PartOfSpeech.cs b/backend/LcmCrdt/Objects/PartOfSpeech.cs new file mode 100644 index 0000000000..997c2c420d --- /dev/null +++ b/backend/LcmCrdt/Objects/PartOfSpeech.cs @@ -0,0 +1,46 @@ +using Crdt; +using Crdt.Entities; +using LcmCrdt.Changes; +using MiniLcm; + +namespace LcmCrdt.Objects; + +public class PartOfSpeech : MiniLcm.PartOfSpeech, IObjectBase +{ + Guid IObjectBase.Id + { + get => Id; + init => Id = value; + } + public DateTimeOffset? DeletedAt { get; set; } + public bool Predefined { get; set; } + public Guid[] GetReferences() + { + return []; + } + + public void RemoveReference(Guid id, Commit commit) + { + } + + public IObjectBase Copy() + { + return new PartOfSpeech + { + Id = Id, + Name = Name, + DeletedAt = DeletedAt, + Predefined = Predefined + }; + } + + public static async Task PredefinedPartsOfSpeech(DataModel dataModel, Guid clientId) + { + //todo load from xml instead of hardcoding + await dataModel.AddChanges(clientId, + [ + new CreatePartOfSpeechChange(new Guid("46e4fe08-ffa0-4c8b-bf98-2c56f38904d9"), new MultiString() { { "en", "Adverb" } }, true) + ], + new Guid("023faebb-711b-4d2f-b34f-a15621fc66bb")); + } +} diff --git a/backend/LcmCrdt/ProjectsService.cs b/backend/LcmCrdt/ProjectsService.cs index bf567f84bf..3153280226 100644 --- a/backend/LcmCrdt/ProjectsService.cs +++ b/backend/LcmCrdt/ProjectsService.cs @@ -1,6 +1,8 @@ -using Crdt.Db; +using Crdt; +using Crdt.Db; using Microsoft.Extensions.DependencyInjection; using MiniLcm; +using PartOfSpeech = LcmCrdt.Objects.PartOfSpeech; namespace LcmCrdt; @@ -37,8 +39,10 @@ public async Task CreateProject(string name, var crdtProject = new CrdtProject(name, sqliteFile); await using var serviceScope = CreateProjectScope(crdtProject); var db = serviceScope.ServiceProvider.GetRequiredService(); - await InitProjectDb(db, new ProjectData(name, id ?? Guid.NewGuid(), ProjectData.GetOriginDomain(domain), Guid.NewGuid())); + var projectData = new ProjectData(name, id ?? Guid.NewGuid(), ProjectData.GetOriginDomain(domain), Guid.NewGuid()); + await InitProjectDb(db, projectData); await serviceScope.ServiceProvider.GetRequiredService().PopulateProjectDataCache(); + await SeedSystemData(serviceScope.ServiceProvider.GetRequiredService(), projectData.ClientId); await (afterCreate?.Invoke(serviceScope.ServiceProvider, crdtProject) ?? Task.CompletedTask); return crdtProject; } @@ -50,6 +54,11 @@ internal static async Task InitProjectDb(CrdtDbContext db, ProjectData data) await db.SaveChangesAsync(); } + internal static async Task SeedSystemData(DataModel dataModel, Guid clientId) + { + await PartOfSpeech.PredefinedPartsOfSpeech(dataModel, clientId); + } + public AsyncServiceScope CreateProjectScope(CrdtProject crdtProject) { var serviceScope = provider.CreateAsyncScope();