Skip to content

Commit

Permalink
implement update and delete apis for ComplexFormTypes and sync them i…
Browse files Browse the repository at this point in the history
…n CrdtFwdataProjectSyncService
  • Loading branch information
hahn-kev committed Nov 29, 2024
1 parent 94ac2a7 commit 367ab33
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 9 deletions.
42 changes: 42 additions & 0 deletions backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,14 @@ public IAsyncEnumerable<ComplexFormType> GetComplexFormTypes()
{
return ComplexFormTypesFlattened.Select(ToComplexFormType).ToAsyncEnumerable();
}

public Task<ComplexFormType?> GetComplexFormType(Guid id)
{
var lexEntryType = ComplexFormTypesFlattened.SingleOrDefault(c => c.Guid == id);
if (lexEntryType is null) return Task.FromResult<ComplexFormType?>(null);
return Task.FromResult<ComplexFormType?>(ToComplexFormType(lexEntryType));
}

private ComplexFormType ToComplexFormType(ILexEntryType t)
{
return new ComplexFormType() { Id = t.Guid, Name = FromLcmMultiString(t.Name) };
Expand All @@ -400,6 +408,40 @@ public async Task<ComplexFormType> CreateComplexFormType(ComplexFormType complex
return ToComplexFormType(ComplexFormTypesFlattened.Single(c => c.Guid == complexFormType.Id));
}

public Task<ComplexFormType> UpdateComplexFormType(Guid id, UpdateObjectInput<ComplexFormType> update)
{
var type = ComplexFormTypesFlattened.SingleOrDefault(c => c.Guid == id);
if (type is null) throw new NullReferenceException($"unable to find complex form type with id {id}");
UndoableUnitOfWorkHelper.DoUsingNewOrCurrentUOW("Update Complex Form Type",
"Revert Complex Form Type",
Cache.ServiceLocator.ActionHandler,
() =>
{
var updateProxy = new UpdateComplexFormTypeProxy(type, null, this);
update.Apply(updateProxy);
});
return Task.FromResult(ToComplexFormType(type));
}

public async Task<ComplexFormType> UpdateComplexFormType(ComplexFormType before, ComplexFormType after)
{
await ComplexFormTypeSync.Sync(before, after, this);
return ToComplexFormType(ComplexFormTypesFlattened.Single(c => c.Guid == after.Id));
}

public async Task DeleteComplexFormType(Guid id)
{
var type = ComplexFormTypesFlattened.SingleOrDefault(c => c.Guid == id);
if (type is null) return;
await Cache.DoUsingNewOrCurrentUOW("Delete Complex Form Type",
"Revert delete",
() =>
{
type.Delete();
return ValueTask.CompletedTask;
});
}

public IAsyncEnumerable<VariantType> GetVariantTypes()
{
return VariantTypes.PossibilitiesOS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,35 @@ namespace FwDataMiniLcmBridge.Api.UpdateProxy;
public record UpdateComplexFormTypeProxy : ComplexFormType
{
private readonly ILexEntryType _lexEntryType;
private readonly ILexEntry _lcmEntry;
private readonly ILexEntry? _lcmEntry;
private readonly FwDataMiniLcmApi _lexboxLcmApi;

[SetsRequiredMembers]
public UpdateComplexFormTypeProxy(ILexEntryType lexEntryType, ILexEntry lcmEntry, FwDataMiniLcmApi lexboxLcmApi)
public UpdateComplexFormTypeProxy(ILexEntryType lexEntryType, ILexEntry? lcmEntry, FwDataMiniLcmApi lexboxLcmApi)
{
_lexEntryType = lexEntryType;
_lcmEntry = lcmEntry;
_lexboxLcmApi = lexboxLcmApi;
Name = new();
Name = base.Name = new();
}

public override Guid Id
{
get => _lexEntryType.Guid;
set
{
if (_lcmEntry is null)
throw new InvalidOperationException("Cannot update complex form type Id on a null entry");
_lexboxLcmApi.RemoveComplexFormType(_lcmEntry, _lexEntryType.Guid);
_lexboxLcmApi.AddComplexFormType(_lcmEntry, value);
}
}

public override required MultiString Name
{
get => new UpdateMultiStringProxy(_lexEntryType.Name, _lexboxLcmApi);
set
{
}
}
}
2 changes: 1 addition & 1 deletion backend/FwLite/FwLiteProjectSync.Tests/Sena3SyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private void ShouldAllBeEquivalentTo(Dictionary<Guid, Entry> crdtEntries, Dictio
//by default the first sync is an import, this will skip that so that the sync will actually sync data
private async Task BypassImport()
{
await _syncService.SaveProjectSnapshot(_fwDataApi.Project, new ([], [], []));
await _syncService.SaveProjectSnapshot(_fwDataApi.Project, CrdtFwdataProjectSyncService.ProjectSnapshot.Empty);
}

//this lets us query entries when there is no writing system
Expand Down
14 changes: 14 additions & 0 deletions backend/FwLite/FwLiteProjectSync.Tests/SyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -500,4 +500,18 @@ public async Task CanCreateAComplexFormAndItsComponentInOneSync()
//one of the entries will be created first, it will try to create the reference to the other but it won't exist yet
await _fixture.SyncService.Sync(_fixture.CrdtApi, _fixture.FwDataApi);
}

[Fact]
public async Task CanCreateAComplexFormTypeAndSyncsIt()
{
//ensure they are synced so a real sync will happen when we want it to
await _fixture.SyncService.Sync(_fixture.CrdtApi, _fixture.FwDataApi);

var complexFormEntry = await _fixture.CrdtApi.CreateComplexFormType(new() { Name = new() { { "en", "complexFormType" } } });

//one of the entries will be created first, it will try to create the reference to the other but it won't exist yet
await _fixture.SyncService.Sync(_fixture.CrdtApi, _fixture.FwDataApi);

_fixture.FwDataApi.GetComplexFormTypes().ToBlockingEnumerable().Should().ContainEquivalentOf(complexFormEntry);
}
}
16 changes: 14 additions & 2 deletions backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ await SaveProjectSnapshot(fwdataApi.Project,
new ProjectSnapshot(
await fwdataApi.GetEntries().ToArrayAsync(),
await fwdataApi.GetPartsOfSpeech().ToArrayAsync(),
await fwdataApi.GetSemanticDomains().ToArrayAsync()));
await fwdataApi.GetSemanticDomains().ToArrayAsync(),
await fwdataApi.GetComplexFormTypes().ToArrayAsync()));
}
return result;
}
Expand Down Expand Up @@ -72,6 +73,10 @@ private async Task<SyncResult> Sync(IMiniLcmApi crdtApi, IMiniLcmApi fwdataApi,
crdtChanges += await SemanticDomainSync.Sync(currentFwDataSemanticDomains, projectSnapshot.SemanticDomains, crdtApi);
fwdataChanges += await SemanticDomainSync.Sync(await crdtApi.GetSemanticDomains().ToArrayAsync(), currentFwDataSemanticDomains, fwdataApi);

var currentFwDataComplexFormTypes = await fwdataApi.GetComplexFormTypes().ToArrayAsync();
crdtChanges += await ComplexFormTypeSync.Sync(currentFwDataComplexFormTypes, projectSnapshot.ComplexFormTypes, crdtApi);
fwdataChanges += await ComplexFormTypeSync.Sync(await crdtApi.GetComplexFormTypes().ToArrayAsync(), currentFwDataComplexFormTypes, fwdataApi);

var currentFwDataEntries = await fwdataApi.GetEntries().ToArrayAsync();
crdtChanges += await EntrySync.Sync(currentFwDataEntries, projectSnapshot.Entries, crdtApi);
LogDryRun(crdtApi, "crdt");
Expand Down Expand Up @@ -100,7 +105,14 @@ private void LogDryRun(IMiniLcmApi api, string type)
return ((DryRunMiniLcmApi)api).DryRunRecords;
}

public record ProjectSnapshot(Entry[] Entries, PartOfSpeech[] PartsOfSpeech, SemanticDomain[] SemanticDomains);
public record ProjectSnapshot(
Entry[] Entries,
PartOfSpeech[] PartsOfSpeech,
SemanticDomain[] SemanticDomains,
ComplexFormType[] ComplexFormTypes)
{
internal static ProjectSnapshot Empty { get; } = new([], [], [], []);
}

private async Task<ProjectSnapshot?> GetProjectSnapshot(FwDataProject project)
{
Expand Down
24 changes: 24 additions & 0 deletions backend/FwLite/FwLiteProjectSync/DryRunMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,37 @@ public IAsyncEnumerable<ComplexFormType> GetComplexFormTypes()
return api.GetComplexFormTypes();
}

public Task<ComplexFormType?> GetComplexFormType(Guid id)
{
return api.GetComplexFormType(id);
}


public Task<ComplexFormType> CreateComplexFormType(ComplexFormType complexFormType)
{
DryRunRecords.Add(new DryRunRecord(nameof(CreateComplexFormType),
$"Create complex form type {complexFormType.Name}"));
return Task.FromResult(complexFormType);
}

public async Task<ComplexFormType> UpdateComplexFormType(Guid id, UpdateObjectInput<ComplexFormType> update)
{
DryRunRecords.Add(new DryRunRecord(nameof(UpdateComplexFormType), $"Update complex form type {id}"));
return await GetComplexFormType(id) ?? throw new NullReferenceException($"unable to find complex form type with id {id}");
}

public Task<ComplexFormType> UpdateComplexFormType(ComplexFormType before, ComplexFormType after)
{
DryRunRecords.Add(new DryRunRecord(nameof(UpdateComplexFormType), $"Update complex form type {after.Id}"));
return Task.FromResult(after);
}

public Task DeleteComplexFormType(Guid id)
{
DryRunRecords.Add(new DryRunRecord(nameof(DeleteComplexFormType), $"Delete complex form type {id}"));
return Task.CompletedTask;
}

public IAsyncEnumerable<Entry> GetEntries(QueryOptions? options = null)
{
return api.GetEntries(options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
DerivedType: JsonPatchChange<SemanticDomain>,
TypeDiscriminator: jsonPatch:SemanticDomain
},
{
DerivedType: JsonPatchChange<ComplexFormType>,
TypeDiscriminator: jsonPatch:ComplexFormType
},
{
DerivedType: DeleteChange<Entry>,
TypeDiscriminator: delete:Entry
Expand Down
4 changes: 3 additions & 1 deletion backend/FwLite/LcmCrdt.Tests/EntityCopyMethodTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public static IEnumerable<object[]> GetEntityTypes()
typeof(ExampleSentence),
typeof(WritingSystem),
typeof(PartOfSpeech),
typeof(SemanticDomain)
typeof(SemanticDomain),
typeof(ComplexFormType)
];
return types.Select(t => new object[] { t });
}
Expand All @@ -37,6 +38,7 @@ public void EntityCopyMethodShouldCopyAllFields(Type type)
type.IsAssignableTo(typeof(IObjectWithId)).Should().BeTrue();
var entity = (IObjectWithId) AutoFaker.Generate(type);
var copy = entity.Copy();
//todo this does not detect a deep copy, but it should as that breaks stuff
copy.Should().BeEquivalentTo(entity, options => options.IncludingAllRuntimeProperties());
}
}
22 changes: 22 additions & 0 deletions backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ public IAsyncEnumerable<ComplexFormType> GetComplexFormTypes()
return ComplexFormTypes.AsAsyncEnumerable();
}

public async Task<ComplexFormType?> GetComplexFormType(Guid id)
{
return await ComplexFormTypes.SingleOrDefaultAsync(c => c.Id == id);
}

public async Task<ComplexFormType> CreateComplexFormType(ComplexFormType complexFormType)
{
await validators.ValidateAndThrow(complexFormType);
Expand All @@ -171,6 +176,23 @@ public async Task<ComplexFormType> CreateComplexFormType(ComplexFormType complex
return await ComplexFormTypes.SingleAsync(c => c.Id == complexFormType.Id);
}

public async Task<ComplexFormType> UpdateComplexFormType(Guid id, UpdateObjectInput<ComplexFormType> update)
{
await dataModel.AddChange(ClientId, new JsonPatchChange<ComplexFormType>(id, update.Patch));
return await GetComplexFormType(id) ?? throw new NullReferenceException($"unable to find complex form type with id {id}");
}

public async Task<ComplexFormType> UpdateComplexFormType(ComplexFormType before, ComplexFormType after)
{
await ComplexFormTypeSync.Sync(before, after, this);
return await GetComplexFormType(after.Id) ?? throw new NullReferenceException($"unable to find complex form type with id {after.Id}");
}

public async Task DeleteComplexFormType(Guid id)
{
await dataModel.AddChange(ClientId, new DeleteChange<ComplexFormType>(id));
}

public async Task<ComplexFormComponent> CreateComplexFormComponent(ComplexFormComponent complexFormComponent)
{
var existing = await ComplexFormComponents.SingleOrDefaultAsync(c =>
Expand Down
1 change: 1 addition & 0 deletions backend/FwLite/LcmCrdt/LcmCrdtKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ public static void ConfigureCrdt(CrdtConfig config)
.Add<JsonPatchChange<WritingSystem>>()
.Add<JsonPatchChange<PartOfSpeech>>()
.Add<JsonPatchChange<SemanticDomain>>()
.Add<JsonPatchChange<ComplexFormType>>()
.Add<DeleteChange<Entry>>()
.Add<DeleteChange<Sense>>()
.Add<DeleteChange<ExampleSentence>>()
Expand Down
19 changes: 19 additions & 0 deletions backend/FwLite/MiniLcm.Tests/ComplexFormComponentTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ public async Task CreateComplexFormType_Works()
types.Should().ContainSingle(t => t.Id == complexFormType.Id);
}

[Fact]
public async Task UpdateComplexFormType_Works()
{
var complexFormType = new ComplexFormType() { Id = Guid.NewGuid(), Name = new() { { "en", "test" } } };
await Api.CreateComplexFormType(complexFormType);
var updatedComplexFormType = await Api.UpdateComplexFormType(complexFormType.Id, new UpdateObjectInput<ComplexFormType>().Set(c => c.Name["en"], "updated"));
updatedComplexFormType.Name["en"].Should().Be("updated");
}

[Fact]
public async Task UpdateComplexFormTypeSync_Works()
{
var complexFormType = new ComplexFormType() { Id = Guid.NewGuid(), Name = new() { { "en", "test" } } };
await Api.CreateComplexFormType(complexFormType);
var afterFormType = complexFormType with { Name = new() { { "en", "updated" } } };
var actualFormType = await Api.UpdateComplexFormType(complexFormType, afterFormType);
actualFormType.Should().BeEquivalentTo(afterFormType, options => options.Excluding(c => c.Id));
}

[Fact]
public async Task AddComplexFormType_Works()
{
Expand Down
1 change: 1 addition & 0 deletions backend/FwLite/MiniLcm/IMiniLcmReadApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public interface IMiniLcmReadApi
IAsyncEnumerable<PartOfSpeech> GetPartsOfSpeech();
IAsyncEnumerable<SemanticDomain> GetSemanticDomains();
IAsyncEnumerable<ComplexFormType> GetComplexFormTypes();
Task<ComplexFormType?> GetComplexFormType(Guid id);
IAsyncEnumerable<Entry> GetEntries(QueryOptions? options = null);
IAsyncEnumerable<Entry> SearchEntries(string query, QueryOptions? options = null);
Task<Entry?> GetEntry(Guid id);
Expand Down
3 changes: 3 additions & 0 deletions backend/FwLite/MiniLcm/IMiniLcmWriteApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ Task<WritingSystem> UpdateWritingSystem(WritingSystemId id,
#endregion

Task<ComplexFormType> CreateComplexFormType(ComplexFormType complexFormType);
Task<ComplexFormType> UpdateComplexFormType(Guid id, UpdateObjectInput<ComplexFormType> update);
Task<ComplexFormType> UpdateComplexFormType(ComplexFormType before, ComplexFormType after);
Task DeleteComplexFormType(Guid id);

#region Entry
Task<Entry> CreateEntry(Entry entry);
Expand Down
4 changes: 2 additions & 2 deletions backend/FwLite/MiniLcm/Models/ComplexFormType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
public record ComplexFormType : IObjectWithId
{
public virtual Guid Id { get; set; }
public required MultiString Name { get; set; }
public virtual required MultiString Name { get; set; }

public DateTimeOffset? DeletedAt { get; set; }

Expand All @@ -19,6 +19,6 @@ public void RemoveReference(Guid id, DateTimeOffset time)

public IObjectWithId Copy()
{
return new ComplexFormType { Id = Id, Name = Name, DeletedAt = DeletedAt };
return new ComplexFormType { Id = Id, Name = Name.Copy(), DeletedAt = DeletedAt };
}
}
47 changes: 47 additions & 0 deletions backend/FwLite/MiniLcm/SyncHelpers/ComplexFormTypeSync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using MiniLcm.Models;
using SystemTextJsonPatch;

namespace MiniLcm.SyncHelpers;

public static class ComplexFormTypeSync
{
public static async Task<int> Sync(ComplexFormType[] afterComplexFormTypes,
ComplexFormType[] beforeComplexFormTypes,
IMiniLcmApi api)
{
return await DiffCollection.Diff(api,
beforeComplexFormTypes,
afterComplexFormTypes,
complexFormType => complexFormType.Id,
static async (api, afterComplexFormType) =>
{
await api.CreateComplexFormType(afterComplexFormType);
return 1;
},
static async (api, beforeComplexFormType) =>
{
await api.DeleteComplexFormType(beforeComplexFormType.Id);
return 1;
},
static (api, beforeComplexFormType, afterComplexFormType) => Sync(beforeComplexFormType, afterComplexFormType, api));
}

public static async Task<int> Sync(ComplexFormType before,
ComplexFormType after,
IMiniLcmApi api)
{
var updateObjectInput = ComplexFormTypeDiffToUpdate(before, after);
if (updateObjectInput is not null) await api.UpdateComplexFormType(after.Id, updateObjectInput);
return updateObjectInput is null ? 0 : 1;
}

public static UpdateObjectInput<ComplexFormType>? ComplexFormTypeDiffToUpdate(ComplexFormType before, ComplexFormType after)
{
JsonPatchDocument<ComplexFormType> patchDocument = new();
patchDocument.Operations.AddRange(MultiStringDiff.GetMultiStringDiff<ComplexFormType>(nameof(ComplexFormType.Name),
before.Name,
after.Name));
if (patchDocument.Operations.Count == 0) return null;
return new UpdateObjectInput<ComplexFormType>(patchDocument);
}
}

0 comments on commit 367ab33

Please sign in to comment.