Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,24 @@

<PropertyGroup>
<Authors>link-foundation</Authors>
<Description>A CLI tool for links manipulation.</Description>
<Description>A CLI tool for links manipulation with REST API support.</Description>
<PackageId>clink</PackageId>
<Version>2.2.2</Version>
<Version>2.3.0</Version>
<PackageLicenseExpression>Unlicense</PackageLicenseExpression>
<RepositoryUrl>https://github.com/link-foundation/link-cli</RepositoryUrl>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Platform.Data" Version="0.16.1" />
<PackageReference Include="Platform.Data.Doublets" Version="0.18.1" />
<PackageReference Include="Platform.Data.Doublets.Sequences" Version="0.6.5" />
<PackageReference Include="Platform.Protocols.Lino" Version="0.4.5" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

<ItemGroup>
Expand Down
214 changes: 214 additions & 0 deletions Foundation.Data.Doublets.Cli/LinksController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Platform.Data.Doublets;
using System.Text;
using DoubletLink = Platform.Data.Doublets.Link<uint>;
using QueryProcessor = Foundation.Data.Doublets.Cli.AdvancedMixedQueryProcessor;

namespace Foundation.Data.Doublets.Cli
{
[ApiController]
[Route("api/[controller]")]
public class LinksController : ControllerBase
{
private readonly string _dbPath;

public LinksController(IConfiguration configuration)
{
_dbPath = configuration.GetValue<string>("Database:Path") ?? "db.links";
}

[HttpGet]
public async Task<IActionResult> GetAllLinks([FromQuery] bool trace = false)
{
try
{
var decoratedLinks = new NamedLinksDecorator<uint>(_dbPath, trace);
var query = "((($i: $s $t)) (($i: $s $t)))";
var result = await ProcessLinoQueryAsync(decoratedLinks, query, trace);
return Ok(result);
}
catch (Exception ex)
{
return BadRequest($"Error: {ex.Message}");
}
}

[HttpPost]
public async Task<IActionResult> CreateLinks([FromBody] LinoRequest request)
{
if (string.IsNullOrWhiteSpace(request.Query))
{
return BadRequest("Query is required");
}

try
{
var decoratedLinks = new NamedLinksDecorator<uint>(_dbPath, request.Trace);
var result = await ProcessLinoQueryAsync(decoratedLinks, request.Query, request.Trace);
return Created("", result);
}
catch (Exception ex)
{
return BadRequest($"Error: {ex.Message}");
}
}

[HttpPut]
public async Task<IActionResult> UpdateLinks([FromBody] LinoRequest request)
{
if (string.IsNullOrWhiteSpace(request.Query))
{
return BadRequest("Query is required");
}

try
{
var decoratedLinks = new NamedLinksDecorator<uint>(_dbPath, request.Trace);
var result = await ProcessLinoQueryAsync(decoratedLinks, request.Query, request.Trace);
return Ok(result);
}
catch (Exception ex)
{
return BadRequest($"Error: {ex.Message}");
}
}

[HttpDelete]
public async Task<IActionResult> DeleteLinks([FromBody] LinoRequest request)
{
if (string.IsNullOrWhiteSpace(request.Query))
{
return BadRequest("Query is required");
}

try
{
var decoratedLinks = new NamedLinksDecorator<uint>(_dbPath, request.Trace);
var result = await ProcessLinoQueryAsync(decoratedLinks, request.Query, request.Trace);
return Ok(result);
}
catch (Exception ex)
{
return BadRequest($"Error: {ex.Message}");
}
}

[HttpPost("query")]
public async Task<IActionResult> ExecuteQuery([FromBody] LinoRequest request)
{
if (string.IsNullOrWhiteSpace(request.Query))
{
return BadRequest("Query is required");
}

try
{
var decoratedLinks = new NamedLinksDecorator<uint>(_dbPath, request.Trace);
var result = await ProcessLinoQueryAsync(decoratedLinks, request.Query, request.Trace);
return Ok(result);
}
catch (Exception ex)
{
return BadRequest($"Error: {ex.Message}");
}
}

private async Task<LinoResponse> ProcessLinoQueryAsync(NamedLinksDecorator<uint> decoratedLinks, string query, bool trace)
{
var changesList = new List<(DoubletLink Before, DoubletLink After)>();
var linksBeforeQuery = GetAllLinksAsString(decoratedLinks);

var options = new QueryProcessor.Options
{
Query = query,
Trace = trace,
ChangesHandler = (beforeLink, afterLink) =>
{
changesList.Add((new DoubletLink(beforeLink), new DoubletLink(afterLink)));
return decoratedLinks.Constants.Continue;
}
};

// Execute the query
await Task.Run(() => QueryProcessor.ProcessQuery(decoratedLinks, options));

var linksAfterQuery = GetAllLinksAsString(decoratedLinks);
var changes = FormatChanges(decoratedLinks, changesList);

return new LinoResponse
{
Query = query,
LinksBefore = linksBeforeQuery,
LinksAfter = linksAfterQuery,
Changes = changes,
ChangeCount = changesList.Count
};
}

private string GetAllLinksAsString(NamedLinksDecorator<uint> links)
{
var sb = new StringBuilder();
var any = links.Constants.Any;
var query = new DoubletLink(index: any, source: any, target: any);

links.Each(query, link =>
{
var formattedLink = links.Format(link);
var namedLink = Namify(links, formattedLink);
sb.AppendLine(namedLink);
return links.Constants.Continue;
});

return sb.ToString().Trim();
}

private List<string> FormatChanges(NamedLinksDecorator<uint> links, List<(DoubletLink Before, DoubletLink After)> changesList)
{
var changes = new List<string>();

foreach (var (linkBefore, linkAfter) in changesList)
{
var beforeText = linkBefore.IsNull() ? "" : links.Format(linkBefore);
var afterText = linkAfter.IsNull() ? "" : links.Format(linkAfter);
var formattedChange = $"({beforeText}) ({afterText})";
changes.Add(Namify(links, formattedChange));
}

return changes;
}

private static string Namify(NamedLinksDecorator<uint> namedLinks, string linksNotation)
{
var numberGlobalRegex = new System.Text.RegularExpressions.Regex(@"\d+");
var matches = numberGlobalRegex.Matches(linksNotation);
var newLinksNotation = linksNotation;
foreach (System.Text.RegularExpressions.Match match in matches)
{
var number = match.Value;
var numberLink = uint.Parse(number);
var name = namedLinks.GetName(numberLink);
if (name != null)
{
newLinksNotation = newLinksNotation.Replace(number, name);
}
}
return newLinksNotation;
}
}

public class LinoRequest
{
public string Query { get; set; } = "";
public bool Trace { get; set; } = false;
}

public class LinoResponse
{
public string Query { get; set; } = "";
public string LinksBefore { get; set; } = "";
public string LinksAfter { get; set; } = "";
public List<string> Changes { get; set; } = new();
public int ChangeCount { get; set; } = 0;
}
}
69 changes: 69 additions & 0 deletions Foundation.Data.Doublets.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
using Platform.Data.Doublets;
using Platform.Data.Doublets.Memory.United.Generic;
using Platform.Protocols.Lino;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;

using static Foundation.Data.Doublets.Cli.ChangesSimplifier;
using DoubletLink = Platform.Data.Doublets.Link<uint>;
Expand Down Expand Up @@ -70,6 +74,30 @@
afterOption.AddAlias("--links");
afterOption.AddAlias("-a");

// Server command options
var portOption = new Option<int>(
name: "--port",
description: "Port for the REST API server",
getDefaultValue: () => 5000
);
portOption.AddAlias("-p");

var hostOption = new Option<string>(
name: "--host",
description: "Host address for the REST API server",
getDefaultValue: () => "localhost"
);
hostOption.AddAlias("-h");

// Create server command
var serverCommand = new Command("serve", "Start REST API server")
{
dbOption,
portOption,
hostOption,
traceOption
};

var rootCommand = new RootCommand("LiNo CLI Tool for managing links data store")
{
dbOption,
Expand All @@ -82,6 +110,47 @@
afterOption
};

rootCommand.AddCommand(serverCommand);

// Server command handler
serverCommand.SetHandler(
async (string db, int port, string host, bool trace) =>
{
Console.WriteLine($"Starting LINO REST API server on {host}:{port}");
Console.WriteLine($"Database: {db}");
Console.WriteLine($"Trace mode: {trace}");

var builder = WebApplication.CreateBuilder();

// Configure services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Configure database path
builder.Configuration["Database:Path"] = db;

// Configure web server
builder.WebHost.UseUrls($"http://{host}:{port}");

var app = builder.Build();

// Configure pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.MapControllers();

Console.WriteLine($"Server started! Visit http://{host}:{port}/swagger for API documentation");

await app.RunAsync();
},
dbOption, portOption, hostOption, traceOption
);

rootCommand.SetHandler(
(string db, string queryOptionValue, string queryArgumentValue, bool trace, uint? structure, bool before, bool changes, bool after) =>
{
Expand Down
Loading