Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use project ID, not code, in CrdtMerge API #1169

Merged
merged 7 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/CrdtMerge/CrdtMerge.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ProjectReference Include="../FwLite/LcmCrdt/LcmCrdt.csproj" />
<ProjectReference Include="../FwLite/FwLiteProjectSync/FwLiteProjectSync.csproj" />
<ProjectReference Include="../FixFwData/FixFwData.csproj" />
<ProjectReference Include="../LexData/LexData.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions backend/CrdtMerge/CrdtMergeKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public static void AddCrdtMerge(this IServiceCollection services)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddScoped<SendReceiveService>();
services.AddScoped<ProjectLookupService>();
services
.AddLcmCrdtClient()
.AddFwDataBridge()
Expand Down
46 changes: 29 additions & 17 deletions backend/CrdtMerge/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using FwDataMiniLcmBridge;
using FwLiteProjectSync;
using LcmCrdt;
using LexData;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.Extensions.Options;
using MiniLcm;
using Scalar.AspNetCore;
Expand All @@ -12,6 +14,11 @@
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();

builder.Services.AddLexData(
autoApplyMigrations: false,
useOpenIddict: false
);

builder.Services.AddCrdtMerge();

var app = builder.Build();
Expand All @@ -30,56 +37,61 @@

app.Run();

static async Task<CrdtFwdataProjectSyncService.SyncResult> ExecuteMergeRequest(
static async Task<Results<Ok<CrdtFwdataProjectSyncService.SyncResult>, NotFound>> ExecuteMergeRequest(
ILogger<Program> logger,
IServiceProvider services,
SendReceiveService srService,
IOptions<CrdtMergeConfig> config,
FwDataFactory fwDataFactory,
ProjectsService projectsService,
ProjectLookupService projectLookupService,
CrdtFwdataProjectSyncService syncService,
string projectCode,
// string projectName, // TODO: Add this to the API eventually
Guid projectId,
bool dryRun = false)
{
logger.LogInformation("About to execute sync request for {projectCode}", projectCode);
logger.LogInformation("About to execute sync request for {projectId}", projectId);
if (dryRun)
{
logger.LogInformation("Dry run, not actually syncing");
return new(0, 0);
return TypedResults.Ok(new CrdtFwdataProjectSyncService.SyncResult(0, 0));
}

var projectCode = await projectLookupService.GetProjectCode(projectId);
if (projectCode is null)
{
logger.LogError("Project ID {projectId} not found", projectId);
return TypedResults.NotFound();
}
logger.LogInformation("Project code is {projectCode}", projectCode);

// TODO: Instead of projectCode here, we'll evetually look up project ID and use $"{projectName}-{projectId}" as the project folder
var projectFolder = Path.Join(config.Value.ProjectStorageRoot, projectCode);
var projectFolder = Path.Join(config.Value.ProjectStorageRoot, $"{projectCode}-{projectId}");
if (!Directory.Exists(projectFolder)) Directory.CreateDirectory(projectFolder);

// TODO: add projectName parameter and use it instead of projectCode here
var crdtFile = Path.Join(projectFolder, $"{projectCode}.sqlite");
var crdtFile = Path.Join(projectFolder, "crdt.sqlite");

var fwDataProject = new FwDataProject(projectCode, projectFolder); // TODO: use projectName (once we have it) instead of projectCode here
var fwDataProject = new FwDataProject("fw", projectFolder);
logger.LogDebug("crdtFile: {crdtFile}", crdtFile);
logger.LogDebug("fwDataFile: {fwDataFile}", fwDataProject.FilePath);

if (File.Exists(fwDataProject.FilePath))
{
var srResult = srService.SendReceive(fwDataProject);
var srResult = srService.SendReceive(fwDataProject, projectCode);
logger.LogInformation("Send/Receive result: {srResult}", srResult.Output);
}
else
{
var srResult = srService.Clone(fwDataProject);
var srResult = srService.Clone(fwDataProject, projectCode);
logger.LogInformation("Send/Receive result: {srResult}", srResult.Output);
}
var fwdataApi = fwDataFactory.GetFwDataMiniLcmApi(fwDataProject, true);
// var crdtProject = projectsService.GetProject(crdtProjectName);
var crdtProject = File.Exists(crdtFile) ?
new CrdtProject(projectCode, crdtFile) : // TODO: use projectName (once we have it) instead of projectCode here
await projectsService.CreateProject(new(projectCode, SeedNewProjectData: false, Path: projectFolder, FwProjectId: fwdataApi.ProjectId));
new CrdtProject("crdt", crdtFile) :
await projectsService.CreateProject(new("crdt", SeedNewProjectData: false, Path: projectFolder, FwProjectId: fwdataApi.ProjectId));
var miniLcmApi = await services.OpenCrdtProject(crdtProject);
var result = await syncService.Sync(miniLcmApi, fwdataApi, dryRun);
logger.LogInformation("Sync result, CrdtChanges: {CrdtChanges}, FwdataChanges: {FwdataChanges}", result.CrdtChanges, result.FwdataChanges);
var srResult2 = srService.SendReceive(fwDataProject);
var srResult2 = srService.SendReceive(fwDataProject, projectCode);
logger.LogInformation("Send/Receive result after CRDT sync: {srResult2}", srResult2.Output);
return result;
return TypedResults.Ok(result);
}

16 changes: 16 additions & 0 deletions backend/CrdtMerge/ProjectLookupService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using LexData;
using Microsoft.EntityFrameworkCore;

namespace CrdtMerge;

public class ProjectLookupService(LexBoxDbContext dbContext)
{
public async ValueTask<string?> GetProjectCode(Guid projectId)
{
var projectCode = await dbContext.Projects
.Where(p => p.Id == projectId)
.Select(p => p.Code)
.FirstOrDefaultAsync();
return projectCode;
}
}
12 changes: 6 additions & 6 deletions backend/CrdtMerge/SendReceiveHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ private static Uri BuildSendReceiveUrl(string baseUrl, string projectCode, SendR
return builder.Uri;
}

public static LfMergeBridgeResult SendReceive(FwDataProject project, string baseUrl = "http://localhost", SendReceiveAuth? auth = null, string fdoDataModelVersion = "7000072", string? commitMessage = null)
public static LfMergeBridgeResult SendReceive(FwDataProject project, string? projectCode = null, string baseUrl = "http://localhost", SendReceiveAuth? auth = null, string fdoDataModelVersion = "7000072", string? commitMessage = null)
{
// If projectCode not given, calculate it from the fwdataPath
projectCode ??= project.Name;
var fwdataInfo = new FileInfo(project.FilePath);
if (fwdataInfo.Directory is null) throw new InvalidOperationException(
$"Not allowed to Send/Receive root-level directories like C:\\, was '{project.FilePath}'");

var repoUrl = BuildSendReceiveUrl(baseUrl, project.Name, auth);
var repoUrl = BuildSendReceiveUrl(baseUrl, projectCode, auth);

var flexBridgeOptions = new Dictionary<string, string>
{
Expand All @@ -68,13 +68,13 @@ public static LfMergeBridgeResult SendReceive(FwDataProject project, string base
return CallLfMergeBridge("Language_Forge_Send_Receive", flexBridgeOptions);
}

public static LfMergeBridgeResult CloneProject(FwDataProject project, string baseUrl = "http://localhost", SendReceiveAuth? auth = null, string fdoDataModelVersion = "7000072")
public static LfMergeBridgeResult CloneProject(FwDataProject project, string? projectCode = null, string baseUrl = "http://localhost", SendReceiveAuth? auth = null, string fdoDataModelVersion = "7000072")
{
// If projectCode not given, calculate it from the fwdataPath
projectCode ??= project.Name;
var fwdataInfo = new FileInfo(project.FilePath);
if (fwdataInfo.Directory is null) throw new InvalidOperationException($"Not allowed to Send/Receive root-level directories like C:\\ '{project.FilePath}'");

var repoUrl = BuildSendReceiveUrl(baseUrl, project.Name, auth);
var repoUrl = BuildSendReceiveUrl(baseUrl, projectCode, auth);

var flexBridgeOptions = new Dictionary<string, string>
{
Expand Down
6 changes: 4 additions & 2 deletions backend/CrdtMerge/SendReceiveService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@ namespace CrdtMerge;

public class SendReceiveService(IOptions<CrdtMergeConfig> config)
{
public SendReceiveHelpers.LfMergeBridgeResult SendReceive(FwDataProject project, string? commitMessage = null)
public SendReceiveHelpers.LfMergeBridgeResult SendReceive(FwDataProject project, string? projectCode, string? commitMessage = null)
{
return SendReceiveHelpers.SendReceive(
project: project,
projectCode: projectCode,
baseUrl: config.Value.HgWebUrl,
auth: new SendReceiveHelpers.SendReceiveAuth(config.Value),
fdoDataModelVersion: config.Value.FdoDataModelVersion,
commitMessage: commitMessage
);
}

public SendReceiveHelpers.LfMergeBridgeResult Clone(FwDataProject project)
public SendReceiveHelpers.LfMergeBridgeResult Clone(FwDataProject project, string? projectCode)
{
return SendReceiveHelpers.CloneProject(
project: project,
projectCode: projectCode,
baseUrl: config.Value.HgWebUrl,
auth: new SendReceiveHelpers.SendReceiveAuth(config.Value),
fdoDataModelVersion: config.Value.FdoDataModelVersion
Expand Down
3 changes: 3 additions & 0 deletions backend/CrdtMerge/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"LexboxPassword": "pass",
"FdoDataModelVersion": "7000072"
},
"DbConfig": {
"LexBoxConnectionString": "Host=localhost;Port=5433;Username=postgres;Password=972b722e63f549938d07bd8c4ee5086c;Database=lexbox;Include Error Detail=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
Expand Down
2 changes: 1 addition & 1 deletion backend/FwLite/FwDataMiniLcmBridge/FwDataFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public FwDataMiniLcmApi GetFwDataMiniLcmApi(string projectName, bool saveOnDispo
return GetFwDataMiniLcmApi(project, saveOnDispose);
}

private string CacheKey(FwDataProject project) => $"{nameof(FwDataFactory)}|{project.FileName}";
private string CacheKey(FwDataProject project) => $"{nameof(FwDataFactory)}|{project.FilePath}";

public FwDataMiniLcmApi GetFwDataMiniLcmApi(FwDataProject project, bool saveOnDispose)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public async Task<SyncResult> Sync(IMiniLcmApi crdtApi, FwDataMiniLcmApi fwdataA
}
var projectSnapshot = await GetProjectSnapshot(fwdataApi.Project.Name, fwdataApi.Project.ProjectsPath);
SyncResult result = await Sync(crdtApi, fwdataApi, dryRun, fwdataApi.EntryCount, projectSnapshot);
fwdataApi.Save();

if (!dryRun)
{
Expand Down
2 changes: 1 addition & 1 deletion backend/FwLite/LcmCrdt.Tests/LcmCrdt.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0"/>
<PackageReference Include="Soenneker.Utils.AutoBogus" Version="2.1.278" />
Expand Down
26 changes: 26 additions & 0 deletions backend/FwLite/LcmCrdt.Tests/OpenProjectTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace LcmCrdt.Tests;

public class OpenProjectTests
{
[Fact]
public async Task OpeningAProjectWorks()
{
var sqliteConnectionString = "OpeningAProjectWorks.sqlite";
var builder = Host.CreateEmptyApplicationBuilder(null);
builder.Services.AddLcmCrdtClient();
using var host = builder.Build();
var services = host.Services;
var asyncScope = services.CreateAsyncScope();
await asyncScope.ServiceProvider.GetRequiredService<ProjectsService>()
.CreateProject(new(Name: "OpeningAProjectWorks", Path: ""));

var miniLcmApi = (CrdtMiniLcmApi)await asyncScope.ServiceProvider.OpenCrdtProject(new CrdtProject("OpeningAProjectWorks", sqliteConnectionString));
miniLcmApi.ProjectData.Name.Should().Be("OpeningAProjectWorks");

await asyncScope.ServiceProvider.GetRequiredService<LcmCrdtDbContext>().Database.EnsureDeletedAsync();
}
}
2 changes: 1 addition & 1 deletion backend/FwLite/LcmCrdt/CurrentProjectService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async ValueTask<ProjectData> GetProjectData()

private static string CacheKey(CrdtProject project)
{
return project.Name + "|ProjectData";
return project.DbPath + "|ProjectData";
}

private static string CacheKey(Guid projectId)
Expand Down
10 changes: 9 additions & 1 deletion backend/FwLite/LcmCrdt/LcmCrdtKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,18 @@ public static void ConfigureCrdt(CrdtConfig config)
.Add<CreateComplexFormType>();
}

public static async Task<IMiniLcmApi> OpenCrdtProject(this IServiceProvider services, CrdtProject project)
public static Task<IMiniLcmApi> OpenCrdtProject(this IServiceProvider services, CrdtProject project)
{
//this method must not be async, otherwise Setting the project scope will not work as expected.
//the project is stored in the async scope, if a new scope is created in this method then it will be gone once the method returns
//making the lcm api unusable
var projectsService = services.GetRequiredService<ProjectsService>();
projectsService.SetProjectScope(project);
return LoadMiniLcmApi(services);
}

private static async Task<IMiniLcmApi> LoadMiniLcmApi(IServiceProvider services)
{
await services.GetRequiredService<CurrentProjectService>().PopulateProjectDataCache();
return services.GetRequiredService<IMiniLcmApi>();
}
Expand Down
3 changes: 2 additions & 1 deletion backend/LexData/DataKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public static class DataKernel
{
public static void AddLexData(this IServiceCollection services,
bool autoApplyMigrations,
bool useOpenIddict = true,
ServiceLifetime dbContextLifeTime = ServiceLifetime.Scoped)
{
services.AddScoped<SeedingData>();
Expand All @@ -17,7 +18,7 @@ public static void AddLexData(this IServiceCollection services,
options.EnableDetailedErrors();
options.UseNpgsql(serviceProvider.GetRequiredService<IOptions<DbConfig>>().Value.LexBoxConnectionString);
options.UseProjectables();
options.UseOpenIddict();
if (useOpenIddict) options.UseOpenIddict();
#if DEBUG
options.EnableSensitiveDataLogging();
#endif
Expand Down
Loading