diff --git a/Enterspeed.Cli.sln b/Enterspeed.Cli.sln new file mode 100644 index 0000000..e91345c --- /dev/null +++ b/Enterspeed.Cli.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32526.322 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Enterspeed.Cli", "src\Enterspeed.Cli\Enterspeed.Cli.csproj", "{06FC2B89-FE26-41AA-887A-B4B794699BC1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {06FC2B89-FE26-41AA-887A-B4B794699BC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06FC2B89-FE26-41AA-887A-B4B794699BC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06FC2B89-FE26-41AA-887A-B4B794699BC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06FC2B89-FE26-41AA-887A-B4B794699BC1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6452797C-FFD6-4FEE-A7D2-1648F1C2918C} + EndGlobalSection +EndGlobal diff --git a/src/Enterspeed.Cli/Api/Domain/CreateDomain.cs b/src/Enterspeed.Cli/Api/Domain/CreateDomain.cs new file mode 100644 index 0000000..832c6b5 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Domain/CreateDomain.cs @@ -0,0 +1,5 @@ +namespace Enterspeed.Cli.Api.Domain; + +internal class CreateDomain +{ +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Domain/DeleteDomain.cs b/src/Enterspeed.Cli/Api/Domain/DeleteDomain.cs new file mode 100644 index 0000000..a250073 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Domain/DeleteDomain.cs @@ -0,0 +1,5 @@ +namespace Enterspeed.Cli.Api.Domain; + +internal class DeleteDomain +{ +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Domain/GetDomains.cs b/src/Enterspeed.Cli/Api/Domain/GetDomains.cs new file mode 100644 index 0000000..7b61831 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Domain/GetDomains.cs @@ -0,0 +1,40 @@ +using Enterspeed.Cli.Domain.Models; +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using RestSharp; + +namespace Enterspeed.Cli.Api.Domain; + +public class GetDomainsRequest : IRequest +{ +} + +public class GetDomainsResponse +{ + public DomainId Id { get; set; } + + public TenantId TenantId { get; set; } + + public string Name { get; set; } + + public string[] Hostnames { get; set; } +} + +public class GetDomainRequestHandler : IRequestHandler +{ + private readonly IEnterspeedClient _enterspeedClient; + + public GetDomainRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(GetDomainsRequest getDomainsRequest, CancellationToken cancellationToken) + { + var request = new RestRequest("tenant/domains") + .AddJsonBody(getDomainsRequest); + + var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); + return response; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Domain/UpdateDomain.cs b/src/Enterspeed.Cli/Api/Domain/UpdateDomain.cs new file mode 100644 index 0000000..f07f1e7 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Domain/UpdateDomain.cs @@ -0,0 +1,21 @@ +using Enterspeed.Cli.Domain.Models; +using MediatR; + +namespace Enterspeed.Cli.Api.Domain; + +public class UpdateDomainRequest : IRequest +{ + public DomainId DomainId { get; set; } + public string Name { get; set; } +} + +public class UpdateDomainResponse +{ + public DomainId Id { get; set; } + + public TenantId TenantId { get; set; } + + public string Name { get; set; } + + public string[] Hostnames { get; set; } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Environment/CreateEnvironment.cs b/src/Enterspeed.Cli/Api/Environment/CreateEnvironment.cs new file mode 100644 index 0000000..9a9b199 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Environment/CreateEnvironment.cs @@ -0,0 +1,34 @@ +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using RestSharp; + +namespace Enterspeed.Cli.Api.Environment +{ + public class CreateEnvironmentRequest : IRequest + { + public string Name { get; set; } + } + + public class CreateEnvironmentResponse + { + } + + public class CreateEnvironmentRequestRequestHandler : IRequestHandler + { + private readonly IEnterspeedClient _enterspeedClient; + + public CreateEnvironmentRequestRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(CreateEnvironmentRequest createEnvironmentsRequest, CancellationToken cancellationToken) + { + var request = new RestRequest("tenant/environments", Method.Post) + .AddJsonBody(createEnvironmentsRequest); + + var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); + return response; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Environment/DeleteEnvironment.cs b/src/Enterspeed.Cli/Api/Environment/DeleteEnvironment.cs new file mode 100644 index 0000000..08bfc11 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Environment/DeleteEnvironment.cs @@ -0,0 +1,14 @@ +using Enterspeed.Cli.Domain.Models; +using MediatR; + +namespace Enterspeed.Cli.Api.Environment +{ + public class DeleteEnvironmentRequest : IRequest + { + public EnvironmentId EnvironmentId { get; set; } + } + + public class DeleteEnvironmentResponse + { + } +} diff --git a/src/Enterspeed.Cli/Api/Environment/GetEnvironments.cs b/src/Enterspeed.Cli/Api/Environment/GetEnvironments.cs new file mode 100644 index 0000000..c20c206 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Environment/GetEnvironments.cs @@ -0,0 +1,38 @@ +using Enterspeed.Cli.Domain.Models; +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using RestSharp; + +namespace Enterspeed.Cli.Api.Environment; + +public class GetEnvironmentsRequest : IRequest +{ +} + +public class GetEnvironmentsResponse +{ + public EnvironmentId Id { get; set; } + + public TenantId TenantId { get; set; } + + public string Name { get; set; } +} + +public class GetEnvironmentsRequestHandler : IRequestHandler +{ + private readonly IEnterspeedClient _enterspeedClient; + + public GetEnvironmentsRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(GetEnvironmentsRequest getEnvironmentsRequest, CancellationToken cancellationToken) + { + var request = new RestRequest("tenant/environments") + .AddJsonBody(getEnvironmentsRequest); + + var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); + return response; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Environment/UpdateEnvironment.cs b/src/Enterspeed.Cli/Api/Environment/UpdateEnvironment.cs new file mode 100644 index 0000000..79e9425 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Environment/UpdateEnvironment.cs @@ -0,0 +1,15 @@ +using Enterspeed.Cli.Domain.Models; +using MediatR; + +namespace Enterspeed.Cli.Api.Environment +{ + public class UpdateEnvironmentRequest : IRequest + { + public EnvironmentId EnvironmentId { get; set; } + public string Name { get; set; } + } + + public class UpdateEnvironmentResponse + { + } +} diff --git a/src/Enterspeed.Cli/Api/EnvironmentClient/GetEnvironmentClients.cs b/src/Enterspeed.Cli/Api/EnvironmentClient/GetEnvironmentClients.cs new file mode 100644 index 0000000..1e6ae79 --- /dev/null +++ b/src/Enterspeed.Cli/Api/EnvironmentClient/GetEnvironmentClients.cs @@ -0,0 +1,41 @@ +using Enterspeed.Cli.Domain.Models; +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using RestSharp; + +namespace Enterspeed.Cli.Api.EnvironmentClient; + +public class GetEnvironmentClientsRequest : IRequest +{ +} + +public class GetEnvironmentClientsResponse +{ + public EnvironmentClientId Id { get; set; } + + public TenantId TenantId { get; set; } + + public string Name { get; set; } + public string AccessKey { get; set; } + public string EnvironmentName { get; set; } + public string[] DomainIds { get; set; } +} + +public class GetEnvironmentClientsRequestHandler : IRequestHandler +{ + private readonly IEnterspeedClient _enterspeedClient; + + public GetEnvironmentClientsRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(GetEnvironmentClientsRequest getEnvironmentsRequest, CancellationToken cancellationToken) + { + var request = new RestRequest("tenant/environment-clients") + .AddJsonBody(getEnvironmentsRequest); + + var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); + return response; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Identity/Authenticate.cs b/src/Enterspeed.Cli/Api/Identity/Authenticate.cs new file mode 100644 index 0000000..74b49ce --- /dev/null +++ b/src/Enterspeed.Cli/Api/Identity/Authenticate.cs @@ -0,0 +1,109 @@ +using System.Net; +using Enterspeed.Cli.Api.Identity.Models; +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; + +namespace Enterspeed.Cli.Api.Identity; + +public class IdentityRequest : IRequest +{ +} + +public class AuthenticationRequestHandler : IRequestHandler +{ + private readonly IEnterspeedClient _enterspeedClient; + + public AuthenticationRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(IdentityRequest request, CancellationToken cancellationToken) + { + var redirectUrl = await GetAuthenticationUrl(); + var authCallback = StartBrowserProcessAndListenForCallback(redirectUrl); + + var response = await _enterspeedClient.Authenticate( + new AuthenticationRequest + { + Type = "authorization_code", + State = authCallback.State, + Token = authCallback.Code + }); + + return await Task.FromResult(response); + } + + private const int CallbackListeningPort = 8081; + + private async Task GetAuthenticationUrl() + { + var redirectUrl = $"http://localhost:{CallbackListeningPort}"; + var response = await _enterspeedClient.Authenticate( + new AuthenticationRequest + { + Type = "authorization_code", + FrontendRedirectUrl = redirectUrl + }, redirectUrl); + + + if (!response.IsValid) + { + return response.Links.SignInUrl; + } + + return null; + } + + private AuthenticationCallback StartBrowserProcessAndListenForCallback(string redirectUrl) + { + string? state = null; + string? code = null; + + var task = Task.Run( + () => + { + using (var listener = new HttpListener()) + { + listener.Prefixes.Add($"http://localhost:{CallbackListeningPort}/"); + listener.Start(); + while (string.IsNullOrEmpty(state) && string.IsNullOrEmpty(code)) + { + HttpListenerContext context = listener.GetContext(); + HttpListenerRequest request = context.Request; + state = request.QueryString.Get("state"); + code = request.QueryString.Get("code"); + + // Obtain a response object. + HttpListenerResponse response = context.Response; + // Construct a response. + byte[] buffer = System.Text.Encoding.UTF8.GetBytes("You can close this window!"); + // Get a response stream and write the response to it. + response.ContentLength64 = buffer.Length; + Stream output = response.OutputStream; + output.Write(buffer, 0, buffer.Length); + output.Close(); + listener.Stop(); + + return Task.CompletedTask; + } + } + + return Task.CompletedTask; + }); + + var psi = new System.Diagnostics.ProcessStartInfo + { + UseShellExecute = true, + FileName = redirectUrl + }; + System.Diagnostics.Process.Start(psi); + + Task.WaitAll(task); + return new AuthenticationCallback() + { + Code = code, + State = state + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Identity/Models/AuthenticationCallback.cs b/src/Enterspeed.Cli/Api/Identity/Models/AuthenticationCallback.cs new file mode 100644 index 0000000..9d7a1c5 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Identity/Models/AuthenticationCallback.cs @@ -0,0 +1,7 @@ +namespace Enterspeed.Cli.Api.Identity.Models; + +public class AuthenticationCallback +{ + public string Code { get; set; } + public string State { get; set; } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Identity/Models/AuthenticationRequest.cs b/src/Enterspeed.Cli/Api/Identity/Models/AuthenticationRequest.cs new file mode 100644 index 0000000..c5f1a10 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Identity/Models/AuthenticationRequest.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace Enterspeed.Cli.Api.Identity.Models; + +public class AuthenticationRequest +{ + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonPropertyName("state")] + public string State { get; set; } + + [JsonPropertyName("token")] + public string Token { get; set; } + + [JsonPropertyName("frontendRedirectUrl")] + public string FrontendRedirectUrl { get; set; } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Identity/Models/AuthenticationResult.cs b/src/Enterspeed.Cli/Api/Identity/Models/AuthenticationResult.cs new file mode 100644 index 0000000..0f9b653 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Identity/Models/AuthenticationResult.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Enterspeed.Cli.Api.Identity.Models; + +public class AuthenticationResult +{ + public bool IsValid { get; set; } + public string RedirectUrl { get; set; } + + [JsonPropertyName("links")] + public MetaLinks Links { get; set; } + + public Token Token { get; set; } + public IdentityUser User { get; set; } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Identity/Models/IdentityUser.cs b/src/Enterspeed.Cli/Api/Identity/Models/IdentityUser.cs new file mode 100644 index 0000000..d590f8e --- /dev/null +++ b/src/Enterspeed.Cli/Api/Identity/Models/IdentityUser.cs @@ -0,0 +1,15 @@ +namespace Enterspeed.Cli.Api.Identity.Models; + +public class IdentityUser +{ + public string Id { get; set; } + + public string FirstName { get; set; } + + public string LastName { get; set; } + + public ICollection Roles { get; set; } + + public Dictionary Tenants { get; set; } + +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Identity/Models/MetaLinks.cs b/src/Enterspeed.Cli/Api/Identity/Models/MetaLinks.cs new file mode 100644 index 0000000..804f6ab --- /dev/null +++ b/src/Enterspeed.Cli/Api/Identity/Models/MetaLinks.cs @@ -0,0 +1,10 @@ +namespace Enterspeed.Cli.Api.Identity.Models; + +public class MetaLinks +{ + public string SignInUrl { get; set; } + public string SignOutUrl { get; set; } + public string SignUpUrl { get; set; } + public string ResetPasswordUrl { get; set; } + public string EditProfileUrl { get; set; } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Identity/Models/Token.cs b/src/Enterspeed.Cli/Api/Identity/Models/Token.cs new file mode 100644 index 0000000..21c4bfd --- /dev/null +++ b/src/Enterspeed.Cli/Api/Identity/Models/Token.cs @@ -0,0 +1,14 @@ +namespace Enterspeed.Cli.Api.Identity.Models; + +public class Token +{ + public string AccessToken { get; set; } + + public string RefreshToken { get; set; } + + public double ExpiresIn { get; set; } + + public string Scope { get; set; } + + public string TokenType { get; set; } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/SourceEntity/QuerySourceEntities.cs b/src/Enterspeed.Cli/Api/SourceEntity/QuerySourceEntities.cs new file mode 100644 index 0000000..1daa931 --- /dev/null +++ b/src/Enterspeed.Cli/Api/SourceEntity/QuerySourceEntities.cs @@ -0,0 +1,62 @@ +using Enterspeed.Cli.Domain.Models; +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using RestSharp; + +namespace Enterspeed.Cli.Api.SourceEntity; + +public class QuerySourceEntitiesRequest : IRequest +{ + public SourceId SourceId { get; set; } + public string Filter { get; set; } + public string Type { get; set; } + + + public int PageSize { get; set; } = 10; +} + +public class QuerySourceEntitiesResponse +{ + //PageInfo + public SourceEntityResponse[] Results { get; set; } +} + +public class SourceEntityResponse +{ + public string Id { get; set; } + public string OriginId { get; set; } + public string OriginParentId { get; set; } + public string SourceName { get; set; } + public string Type { get; set; } + public string Url { get; set; } + public DateTime? UpdatedAt { get; set; } +} + +public class QuerySourceEntitiesRequestHandler : IRequestHandler +{ + private readonly IEnterspeedClient _enterspeedClient; + + public QuerySourceEntitiesRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(QuerySourceEntitiesRequest queryRequest, CancellationToken cancellationToken) + { + var request = new RestRequest($"tenant/sources/{queryRequest.SourceId.SourceGuid}/entities"); + request.AddParameter("first", queryRequest.PageSize); + + if (!string.IsNullOrEmpty(queryRequest.Filter)) + { + request.AddParameter("term", queryRequest.Filter); + } + + if (!string.IsNullOrEmpty(queryRequest.Type)) + { + request.AddParameter("typeTerm", queryRequest.Type); + } + + var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); + return response; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/SourceGroup/GetSourceGroups.cs b/src/Enterspeed.Cli/Api/SourceGroup/GetSourceGroups.cs new file mode 100644 index 0000000..f5d4d20 --- /dev/null +++ b/src/Enterspeed.Cli/Api/SourceGroup/GetSourceGroups.cs @@ -0,0 +1,55 @@ +using Enterspeed.Cli.Api.Environment; +using Enterspeed.Cli.Domain.Models; +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using RestSharp; + +namespace Enterspeed.Cli.Api.SourceGroup; + +public class GetSourceGroupsRequest : IRequest +{ + public string[] TenantIds { get; set; } +} + +public class GetSourceGroupResponse +{ + public string Name { get; set; } + public string Alias { get; set; } + public string Type { get; set; } + public SourceGroupId Id { get; set; } + public TenantId TenantId { get; set; } + public SourceResponse[] Sources { get; set; } +} + +public class SourceResponse +{ + public int EntitiesInSource { get; set; } + + public GetEnvironmentsResponse[] Environments { get; set; } + public Source Source { get; set; } +} + +public class Source +{ + public string Name { get; set; } + public string Type { get; set; } + public SourceId Id { get; set; } +} + +public class GetTenantsRequestHandler : IRequestHandler +{ + private readonly IEnterspeedClient _enterspeedClient; + + public GetTenantsRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(GetSourceGroupsRequest getSourceGroupRequest, CancellationToken cancellationToken) + { + var request = new RestRequest("tenant/source-groups"); + + var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); + return response; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/Tenant/GetTenants.cs b/src/Enterspeed.Cli/Api/Tenant/GetTenants.cs new file mode 100644 index 0000000..c5348f5 --- /dev/null +++ b/src/Enterspeed.Cli/Api/Tenant/GetTenants.cs @@ -0,0 +1,41 @@ +using Enterspeed.Cli.Domain.Models; +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using RestSharp; + +namespace Enterspeed.Cli.Api.Tenant; + +public class GetTenantsRequest : IRequest +{ + public string[] TenantIds { get; set; } +} + +public class GetTenantsResponse +{ + public TenantId Id { get; set; } + public string Name { get; set; } + public bool IsUsingSchemasBulkDeployment { get; set; } + public bool IsUsingSourceGroups { get; set; } +} + +public class GetTenantsRequestHandler : IRequestHandler +{ + private readonly IEnterspeedClient _enterspeedClient; + + public GetTenantsRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(GetTenantsRequest getTenantsRequest, CancellationToken cancellationToken) + { + var request = new RestRequest("tenants"); + foreach (var id in getTenantsRequest.TenantIds) + { + request.AddParameter("ids", id); + } + + var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); + return response; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Api/View/QueryViews.cs b/src/Enterspeed.Cli/Api/View/QueryViews.cs new file mode 100644 index 0000000..981bbfb --- /dev/null +++ b/src/Enterspeed.Cli/Api/View/QueryViews.cs @@ -0,0 +1,65 @@ +using Enterspeed.Cli.Domain.Models; +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using RestSharp; + +namespace Enterspeed.Cli.Api.View; + +public class QueryViewsRequest : IRequest +{ + public EnvironmentId EnvironmentId { get; set; } + public SourceId SourceId { get; set; } + public string SchemaAlias { get; set; } + public string SourceEntityOriginId { get; set; } + + + public int PageSize { get; set; } = 10; +} + +public class QueryViewsResponse +{ + //PageInfo + public ViewResponse[] Results { get; set; } +} + +public class ViewResponse +{ + public ViewId Id { get; set; } + public SourceEntityId SourceEntityId { get; set; } + public string SourceName { get; set; } + public DateTime? UpdatedAt { get; set; } +} + +public class QueryViewsRequestHandler : IRequestHandler +{ + private readonly IEnterspeedClient _enterspeedClient; + + public QueryViewsRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(QueryViewsRequest queryRequest, CancellationToken cancellationToken) + { + var request = new RestRequest($"tenant/environments/{queryRequest.EnvironmentId.EnvironmentGuid}/views"); + request.AddParameter("first", queryRequest.PageSize); + + if (queryRequest.SourceId != null) + { + request.AddParameter("sourceGuid", queryRequest.SourceId.SourceGuid); + } + + if (!string.IsNullOrEmpty(queryRequest.SchemaAlias)) + { + request.AddParameter("schemaAlias", queryRequest.SchemaAlias); + } + + if (!string.IsNullOrEmpty(queryRequest.SourceEntityOriginId)) + { + request.AddParameter("sourceEntityOriginId", queryRequest.SourceEntityOriginId); + } + + var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); + return response; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/BaseCommandHandler.cs b/src/Enterspeed.Cli/Commands/BaseCommandHandler.cs new file mode 100644 index 0000000..c8387c7 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/BaseCommandHandler.cs @@ -0,0 +1,30 @@ +using System.CommandLine.Invocation; + +namespace Enterspeed.Cli.Commands; + +public class BaseCommandHandler +{ + public OutputStyle Output { get; set; } + public bool Yes { get; set; } + + public BaseCommandHandler() + { + + } + + public int Invoke(InvocationContext context) + { + throw new NotImplementedException(); + } + + public bool GetConfirmation() + { + if (Yes) return true; + + Console.WriteLine("Are you sure?"); + var confirm = Console.ReadLine(); + if (confirm != null && (confirm.ToLower() == "y" || confirm.ToLower() == "yes")) + return true; + return false; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/ConfirmCommand.cs b/src/Enterspeed.Cli/Commands/ConfirmCommand.cs new file mode 100644 index 0000000..046b66b --- /dev/null +++ b/src/Enterspeed.Cli/Commands/ConfirmCommand.cs @@ -0,0 +1,12 @@ +using System.CommandLine; + +namespace Enterspeed.Cli.Commands +{ + public class ConfirmCommand : Command + { + public ConfirmCommand(string name, string description) : base(name, description) + { + AddOption(new Option(new[] {"-y", "--yes"}, "Do not prompt for confirmation.")); + } + } +} diff --git a/src/Enterspeed.Cli/Commands/Domain/CreateDomainCommand.cs b/src/Enterspeed.Cli/Commands/Domain/CreateDomainCommand.cs new file mode 100644 index 0000000..a7fad65 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Domain/CreateDomainCommand.cs @@ -0,0 +1,31 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; + +namespace Enterspeed.Cli.Commands.Domain; + +internal class CreateDomainCommand : Command +{ + public CreateDomainCommand() : base(name: "create", "Create domain") + { + AddOption(new Option(new[] { "--name", "-n" }, "Name of domain") {IsRequired = true}); + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Domain/DeleteDomainCommand.cs b/src/Enterspeed.Cli/Commands/Domain/DeleteDomainCommand.cs new file mode 100644 index 0000000..f89441c --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Domain/DeleteDomainCommand.cs @@ -0,0 +1,39 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; + +namespace Enterspeed.Cli.Commands.Domain; + +public class DeleteDomainCommand : ConfirmCommand +{ + public DeleteDomainCommand() : base(name: "delete", "Delete domain") + { + AddArgument(new Argument("id", "Id of the domain") { Arity = ArgumentArity.ZeroOrOne }); + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public string Id { get; set; } + + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + if (!Yes && !GetConfirmation()) + { + return 0; + } + + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Domain/DomainCommands.cs b/src/Enterspeed.Cli/Commands/Domain/DomainCommands.cs new file mode 100644 index 0000000..a46e168 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Domain/DomainCommands.cs @@ -0,0 +1,19 @@ +using System.CommandLine; + +namespace Enterspeed.Cli.Commands.Domain; + +public static class DomainCommands +{ + public static Command BuildCommands() + { + var domain = new Command("domain", "Domain") + { + new GetDomainCommand(), + new ListDomainsCommand(), + new CreateDomainCommand(), + new UpdateDomainCommand(), + new DeleteDomainCommand() + }; + return domain; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Domain/GetDomainCommand.cs b/src/Enterspeed.Cli/Commands/Domain/GetDomainCommand.cs new file mode 100644 index 0000000..2015daf --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Domain/GetDomainCommand.cs @@ -0,0 +1,57 @@ +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Domain; +using Enterspeed.Cli.Exceptions; +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; + +namespace Enterspeed.Cli.Commands.Domain; + +public class GetDomainCommand : Command +{ + public GetDomainCommand() : base(name: "get", "Get a domain") + { + AddArgument(new Argument("id", "Id of the domain") {Arity = ArgumentArity.ZeroOrOne}); + AddOption(new Option(new [] {"--name", "-n"}, "Name of domain")); + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public Guid Id { get; set; } + public string Name { get; set; } + + public async Task InvokeAsync(InvocationContext context) + { + if (Id == Guid.Empty) + { + if (string.IsNullOrEmpty(Name)) + { + throw new ConsoleArgumentException("Please specify either id or name option"); + } + } + + var domains = await _mediator.Send(new GetDomainsRequest()); + GetDomainsResponse result; + if (Id != Guid.Empty) + { + result = domains?.FirstOrDefault(x => x.Id.DomainGuid == Id); + } + else + { + result = domains?.FirstOrDefault(x => x.Name.Equals(Name, StringComparison.InvariantCulture)); + } + + _outputService.Write(result, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Domain/ListDomainsCommand.cs b/src/Enterspeed.Cli/Commands/Domain/ListDomainsCommand.cs new file mode 100644 index 0000000..f9ce6f1 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Domain/ListDomainsCommand.cs @@ -0,0 +1,35 @@ +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Domain; +using Enterspeed.Cli.Commands.Environment; +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; + +namespace Enterspeed.Cli.Commands.Domain; + +public class ListDomainsCommand : Command +{ + public ListDomainsCommand() : base(name: "list", "List domains") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + var domains = await _mediator.Send(new GetDomainsRequest()); + + _outputService.Write(domains, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Domain/UpdateDomainCommand.cs b/src/Enterspeed.Cli/Commands/Domain/UpdateDomainCommand.cs new file mode 100644 index 0000000..e1cef91 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Domain/UpdateDomainCommand.cs @@ -0,0 +1,30 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; + +namespace Enterspeed.Cli.Commands.Domain; + +internal class UpdateDomainCommand : Command +{ + public UpdateDomainCommand() : base(name: "update", "Update domain") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Environment/CreateEnvironmentCommand.cs b/src/Enterspeed.Cli/Commands/Environment/CreateEnvironmentCommand.cs new file mode 100644 index 0000000..a7a1290 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Environment/CreateEnvironmentCommand.cs @@ -0,0 +1,31 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Environment; + +namespace Enterspeed.Cli.Commands.Environment; + +internal class CreateEnvironmentCommand : Command +{ + public CreateEnvironmentCommand() : base(name: "create", "Create environment") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Environment/DeleteEnvironmentCommand.cs b/src/Enterspeed.Cli/Commands/Environment/DeleteEnvironmentCommand.cs new file mode 100644 index 0000000..b9d8896 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Environment/DeleteEnvironmentCommand.cs @@ -0,0 +1,31 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Environment; + +namespace Enterspeed.Cli.Commands.Environment; + +internal class DeleteEnvironmentCommand : Command +{ + public DeleteEnvironmentCommand() : base(name: "delete", "Delete environment") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Environment/EnvironmentCommands.cs b/src/Enterspeed.Cli/Commands/Environment/EnvironmentCommands.cs new file mode 100644 index 0000000..770a78b --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Environment/EnvironmentCommands.cs @@ -0,0 +1,19 @@ +using System.CommandLine; + +namespace Enterspeed.Cli.Commands.Environment; + +public static class EnvironmentCommands +{ + public static Command BuildCommands() + { + var command = new Command("environment", "Environment") + { + new GetEnvironmentCommand(), + new ListEnvironmentsCommand(), + new CreateEnvironmentCommand(), + new UpdateEnvironmentCommand(), + new DeleteEnvironmentCommand() + }; + return command; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Environment/GetEnvironmentCommand.cs b/src/Enterspeed.Cli/Commands/Environment/GetEnvironmentCommand.cs new file mode 100644 index 0000000..5d67dbb --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Environment/GetEnvironmentCommand.cs @@ -0,0 +1,34 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Environment; + +namespace Enterspeed.Cli.Commands.Environment; + +internal class GetEnvironmentCommand : Command +{ + public GetEnvironmentCommand() : base(name: "get", "Get an environment") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + var environments = await _mediator.Send(new GetEnvironmentsRequest()); + + _outputService.Write(environments, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Environment/ListEnvironmentsCommand.cs b/src/Enterspeed.Cli/Commands/Environment/ListEnvironmentsCommand.cs new file mode 100644 index 0000000..d275e9c --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Environment/ListEnvironmentsCommand.cs @@ -0,0 +1,34 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Environment; + +namespace Enterspeed.Cli.Commands.Environment; + +public class ListEnvironmentsCommand : Command +{ + public ListEnvironmentsCommand() : base(name: "list", "List environments") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + var environments = await _mediator.Send(new GetEnvironmentsRequest()); + + _outputService.Write(environments, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Environment/UpdateEnvironmentCommand.cs b/src/Enterspeed.Cli/Commands/Environment/UpdateEnvironmentCommand.cs new file mode 100644 index 0000000..fcc0a1a --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Environment/UpdateEnvironmentCommand.cs @@ -0,0 +1,31 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Environment; + +namespace Enterspeed.Cli.Commands.Environment; + +internal class UpdateEnvironmentCommand : Command +{ + public UpdateEnvironmentCommand() : base(name: "update", "Update environment") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/EnvironmentClient/CreateEnvironmentClientCommand.cs b/src/Enterspeed.Cli/Commands/EnvironmentClient/CreateEnvironmentClientCommand.cs new file mode 100644 index 0000000..3fca3b5 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/EnvironmentClient/CreateEnvironmentClientCommand.cs @@ -0,0 +1,31 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Environment; + +namespace Enterspeed.Cli.Commands.EnvironmentClient; + +internal class CreateEnvironmentClientCommand : Command +{ + public CreateEnvironmentClientCommand() : base(name: "create", "Create environment client") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/EnvironmentClient/DeleteEnvironmentClientCommand.cs b/src/Enterspeed.Cli/Commands/EnvironmentClient/DeleteEnvironmentClientCommand.cs new file mode 100644 index 0000000..e034de8 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/EnvironmentClient/DeleteEnvironmentClientCommand.cs @@ -0,0 +1,31 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Environment; + +namespace Enterspeed.Cli.Commands.EnvironmentClient; + +internal class DeleteEnvironmentClientCommand : Command +{ + public DeleteEnvironmentClientCommand() : base(name: "delete", "Delete environment client") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/EnvironmentClient/EnvironmentClientCommands.cs b/src/Enterspeed.Cli/Commands/EnvironmentClient/EnvironmentClientCommands.cs new file mode 100644 index 0000000..b8f8d46 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/EnvironmentClient/EnvironmentClientCommands.cs @@ -0,0 +1,19 @@ +using System.CommandLine; + +namespace Enterspeed.Cli.Commands.EnvironmentClient; + +public static class EnvironmentClientCommands +{ + public static Command BuildCommands() + { + var command = new Command("environment-client", "Environment client") + { + new GetEnvironmentClientCommand(), + new ListEnvironmentClientsCommand(), + new CreateEnvironmentClientCommand(), + new UpdateEnvironmentClientCommand(), + new DeleteEnvironmentClientCommand() + }; + return command; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/EnvironmentClient/GetEnvironmentClientCommand.cs b/src/Enterspeed.Cli/Commands/EnvironmentClient/GetEnvironmentClientCommand.cs new file mode 100644 index 0000000..f2cb532 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/EnvironmentClient/GetEnvironmentClientCommand.cs @@ -0,0 +1,34 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Environment; + +namespace Enterspeed.Cli.Commands.EnvironmentClient; + +internal class GetEnvironmentClientCommand : Command +{ + public GetEnvironmentClientCommand() : base(name: "get", "Get an environment client") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + var environments = await _mediator.Send(new GetEnvironmentsRequest()); + + _outputService.Write(environments, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/EnvironmentClient/ListEnvironmentClientsCommand.cs b/src/Enterspeed.Cli/Commands/EnvironmentClient/ListEnvironmentClientsCommand.cs new file mode 100644 index 0000000..66cc4a7 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/EnvironmentClient/ListEnvironmentClientsCommand.cs @@ -0,0 +1,34 @@ +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.EnvironmentClient; +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; + +namespace Enterspeed.Cli.Commands.EnvironmentClient; + +public class ListEnvironmentClientsCommand : Command +{ + public ListEnvironmentClientsCommand() : base(name: "list", "List environment clients") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + var environmentClients = await _mediator.Send(new GetEnvironmentClientsRequest()); + + _outputService.Write(environmentClients, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/EnvironmentClient/UpdateEnvironmentClientCommand.cs b/src/Enterspeed.Cli/Commands/EnvironmentClient/UpdateEnvironmentClientCommand.cs new file mode 100644 index 0000000..c562d9c --- /dev/null +++ b/src/Enterspeed.Cli/Commands/EnvironmentClient/UpdateEnvironmentClientCommand.cs @@ -0,0 +1,31 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Environment; + +namespace Enterspeed.Cli.Commands.EnvironmentClient; + +internal class UpdateEnvironmentClientCommand : Command +{ + public UpdateEnvironmentClientCommand() : base(name: "Update", "Update an environment client") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Login/LoginCommand.cs b/src/Enterspeed.Cli/Commands/Login/LoginCommand.cs new file mode 100644 index 0000000..c562ce1 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Login/LoginCommand.cs @@ -0,0 +1,37 @@ +using MediatR; +using System.CommandLine.Invocation; +using System.CommandLine; +using Enterspeed.Cli.Api.Identity; + +namespace Enterspeed.Cli.Commands.Login; + +public class LoginCommand : Command +{ + public LoginCommand() : base(name: "login", "Login using OAuth") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + + public Handler(IMediator mediator) => + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + + public async Task InvokeAsync(InvocationContext context) + { + Console.WriteLine($"Invoke Login"); + + var response = await _mediator.Send(new IdentityRequest()); + + Console.WriteLine($"AccessToken: {response.Token.AccessToken}"); + Console.WriteLine("Tenants:"); + foreach (var tenant in response.User.Tenants) + { + Console.WriteLine($"{tenant.Key} : {string.Join(", ",tenant.Value)}"); + } + + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/SourceEntity/ListSourceEntitiesCommand.cs b/src/Enterspeed.Cli/Commands/SourceEntity/ListSourceEntitiesCommand.cs new file mode 100644 index 0000000..b734a05 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/SourceEntity/ListSourceEntitiesCommand.cs @@ -0,0 +1,47 @@ +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.SourceEntity; +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; + +namespace Enterspeed.Cli.Commands.SourceEntity; + +public class ListSourceEntitiesCommand : Command +{ + public ListSourceEntitiesCommand() : base(name: "list", "List source entities") + { + AddArgument(new Argument("SourceId", "Id of the source") { Arity = ArgumentArity.ExactlyOne }); + AddOption(new Option("--Filter", "Filter on ID or Url")); + AddOption(new Option("--Type", "Source Entity type")); + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public string SourceId { get; set; } + public string Filter { get; set; } + public string Type { get; set; } + + public async Task InvokeAsync(InvocationContext context) + { + var viewsResponse = await _mediator.Send(new QuerySourceEntitiesRequest + { + SourceId = Cli.Domain.Models.SourceId.Parse(Cli.Domain.Models.SourceId.From(SourceId)), + Filter = Filter, + Type = Type, + PageSize = 10 + }); + + _outputService.Write(viewsResponse.Results, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/SourceEntity/SourceEntityCommands.cs b/src/Enterspeed.Cli/Commands/SourceEntity/SourceEntityCommands.cs new file mode 100644 index 0000000..e2f981b --- /dev/null +++ b/src/Enterspeed.Cli/Commands/SourceEntity/SourceEntityCommands.cs @@ -0,0 +1,15 @@ +using System.CommandLine; + +namespace Enterspeed.Cli.Commands.SourceEntity; + +public static class SourceEntityCommands +{ + public static Command BuildCommands() + { + var command = new Command("source-entity", "Source entities") + { + new ListSourceEntitiesCommand() + }; + return command; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/SourceGroup/ListSourceGroupsCommand.cs b/src/Enterspeed.Cli/Commands/SourceGroup/ListSourceGroupsCommand.cs new file mode 100644 index 0000000..334bd94 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/SourceGroup/ListSourceGroupsCommand.cs @@ -0,0 +1,34 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine.Invocation; +using System.CommandLine; +using Enterspeed.Cli.Api.SourceGroup; + +namespace Enterspeed.Cli.Commands.SourceGroup; + +public class ListSourceGroupsCommand : Command +{ + public ListSourceGroupsCommand() : base(name: "list", "List source groups") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public async Task InvokeAsync(InvocationContext context) + { + var sourceGroups = await _mediator.Send(new GetSourceGroupsRequest()); + + _outputService.Write(sourceGroups, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/SourceGroup/SourceGroupCommands.cs b/src/Enterspeed.Cli/Commands/SourceGroup/SourceGroupCommands.cs new file mode 100644 index 0000000..3a620fe --- /dev/null +++ b/src/Enterspeed.Cli/Commands/SourceGroup/SourceGroupCommands.cs @@ -0,0 +1,15 @@ +using System.CommandLine; + +namespace Enterspeed.Cli.Commands.SourceGroup; + +public static class SourceGroupCommands +{ + public static Command BuildCommands() + { + var command = new Command("source-groups", "Source groups") + { + new ListSourceGroupsCommand() + }; + return command; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Tenant/ListTenantsCommand.cs b/src/Enterspeed.Cli/Commands/Tenant/ListTenantsCommand.cs new file mode 100644 index 0000000..3a1456a --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Tenant/ListTenantsCommand.cs @@ -0,0 +1,43 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.Tenant; +using Enterspeed.Cli.Domain.Models; +using Enterspeed.Cli.Services.StateService; + +namespace Enterspeed.Cli.Commands.Tenant; + +public class ListTenantsCommand : Command +{ + public ListTenantsCommand() : base(name: "list", "List tenants") + { + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + private readonly IStateService _stateService; + + public Handler(IMediator mediator, IOutputService outputService, IStateService stateService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + _stateService = stateService; + } + + public async Task InvokeAsync(InvocationContext context) + { + var tenantIds = _stateService.User?.Tenants?.Keys; + + if (tenantIds == null || !tenantIds.Any()) return 0; + var tenantGuids = tenantIds.Select(TenantId.Parse).Select(x => x.TenantGuid).ToArray(); + + var tenants = await _mediator.Send(new GetTenantsRequest {TenantIds = tenantGuids }); + + _outputService.Write(tenants, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Tenant/SetActiveTenantCommand.cs b/src/Enterspeed.Cli/Commands/Tenant/SetActiveTenantCommand.cs new file mode 100644 index 0000000..97901b7 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Tenant/SetActiveTenantCommand.cs @@ -0,0 +1,53 @@ +using Enterspeed.Cli.Domain.Models; +using Enterspeed.Cli.Services.StateService; +using System.CommandLine.Invocation; +using System.CommandLine; +using Microsoft.Extensions.Logging; + +namespace Enterspeed.Cli.Commands.Tenant; + +public class SetActiveTenantCommand : Command +{ + public SetActiveTenantCommand() : base(name: "set", "Set the active tenant") + { + AddArgument(new Argument("id", "Id of the tenant") { }); + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IStateService _stateService; + private readonly ILogger _logger; + + public Handler(IStateService stateService, ILogger logger) + { + _stateService = stateService; + _logger = logger; + } + + public string Id { get; set; } + + public Task InvokeAsync(InvocationContext context) + { + var tenantIds = _stateService.User?.Tenants?.Keys; + + if (tenantIds == null || !tenantIds.Any()) + { + _logger.LogError("Could not find tenant"); + return Task.FromResult(1); + } + var tenants = tenantIds.Select(TenantId.Parse); + + var activeTenant = tenants.FirstOrDefault(x => x.TenantGuid == Id); + if (activeTenant == null) + { + _logger.LogError("Could not find tenant"); + return Task.FromResult(1); + } + + _logger.LogInformation($"Setting active tenant to: {activeTenant.IdValue}"); + _stateService.SetActiveTenant(activeTenant); + + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Tenant/TenantCommands.cs b/src/Enterspeed.Cli/Commands/Tenant/TenantCommands.cs new file mode 100644 index 0000000..c577fd6 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/Tenant/TenantCommands.cs @@ -0,0 +1,16 @@ +using System.CommandLine; + +namespace Enterspeed.Cli.Commands.Tenant; + +public static class TenantCommands +{ + public static Command BuildCommands() + { + var command = new Command("tenant", "Tenant") + { + new ListTenantsCommand(), + new SetActiveTenantCommand() + }; + return command; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/View/ListViewsCommand.cs b/src/Enterspeed.Cli/Commands/View/ListViewsCommand.cs new file mode 100644 index 0000000..a648929 --- /dev/null +++ b/src/Enterspeed.Cli/Commands/View/ListViewsCommand.cs @@ -0,0 +1,51 @@ +using Enterspeed.Cli.Services.ConsoleOutput; +using MediatR; +using System.CommandLine; +using System.CommandLine.Invocation; +using Enterspeed.Cli.Api.View; + +namespace Enterspeed.Cli.Commands.View; + +public class ListViewsCommand : Command +{ + public ListViewsCommand() : base(name: "list", "List views") + { + AddArgument(new Argument("EnvironmentId", "Id of the environment") { Arity = ArgumentArity.ExactlyOne }); + AddOption(new Option("--SourceId", "Source ID")); + AddOption(new Option("--SchemaAlias", "Schema Alias")); + AddOption(new Option("--OriginId", "Source Entity Origin ID")); + } + + public new class Handler : BaseCommandHandler, ICommandHandler + { + private readonly IMediator _mediator; + private readonly IOutputService _outputService; + + public Handler(IMediator mediator, IOutputService outputService) + { + _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); + _outputService = outputService; + } + + public string EnvironmentId { get; set; } + public string SourceId { get; set; } + public string SchemaAlias { get; set; } + public string OriginId { get; set; } + + public async Task InvokeAsync(InvocationContext context) + { + var viewsResponse = await _mediator.Send(new QueryViewsRequest + { + EnvironmentId = Cli.Domain.Models.EnvironmentId.Parse(Cli.Domain.Models.EnvironmentId.From(EnvironmentId)), + SourceId = !string.IsNullOrEmpty(SourceId) ? Cli.Domain.Models.SourceId.Parse(Cli.Domain.Models.SourceId.From(SourceId)) : null, + SchemaAlias = SchemaAlias, + SourceEntityOriginId = OriginId, + PageSize = 10 + + }); + + _outputService.Write(viewsResponse.Results, Output); + return 0; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/View/ViewCommands.cs b/src/Enterspeed.Cli/Commands/View/ViewCommands.cs new file mode 100644 index 0000000..bd82e3f --- /dev/null +++ b/src/Enterspeed.Cli/Commands/View/ViewCommands.cs @@ -0,0 +1,16 @@ +using Enterspeed.Cli.Commands.EnvironmentClient; +using System.CommandLine; + +namespace Enterspeed.Cli.Commands.View; + +public static class ViewCommands +{ + public static Command BuildCommands() + { + var command = new Command("views", "Generated views") + { + new ListViewsCommand() + }; + return command; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Common/Behaviours/LoggingBehaviour.cs b/src/Enterspeed.Cli/Common/Behaviours/LoggingBehaviour.cs new file mode 100644 index 0000000..0780f0c --- /dev/null +++ b/src/Enterspeed.Cli/Common/Behaviours/LoggingBehaviour.cs @@ -0,0 +1,23 @@ +using MediatR.Pipeline; +using Microsoft.Extensions.Logging; + +namespace Enterspeed.Cli.Common.Behaviours; + +public class LoggingBehaviour : IRequestPreProcessor +{ + private readonly ILogger _logger; + + public LoggingBehaviour(ILogger logger) + { + _logger = logger; + } + + public Task Process(TRequest request, CancellationToken cancellationToken) + { + var requestName = typeof(TRequest).Name; + + _logger.LogInformation("Request: {Name} {@Request}", + requestName, request); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Common/Behaviours/UnhandledExceptionBehaviour.cs b/src/Enterspeed.Cli/Common/Behaviours/UnhandledExceptionBehaviour.cs new file mode 100644 index 0000000..b9f9d83 --- /dev/null +++ b/src/Enterspeed.Cli/Common/Behaviours/UnhandledExceptionBehaviour.cs @@ -0,0 +1,27 @@ +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Enterspeed.Cli.Common.Behaviours; + +public class UnhandledExceptionBehaviour : IPipelineBehavior where TRequest : IRequest +{ + private readonly ILogger _logger; + + public UnhandledExceptionBehaviour(ILogger logger) => _logger = logger; + + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + try + { + return await next(); + } + catch (Exception ex) + { + var requestName = typeof(TRequest).Name; + + _logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); + + throw; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Configuration/Settings.cs b/src/Enterspeed.Cli/Configuration/Settings.cs new file mode 100644 index 0000000..17d90c6 --- /dev/null +++ b/src/Enterspeed.Cli/Configuration/Settings.cs @@ -0,0 +1,7 @@ +namespace Enterspeed.Cli.Configuration +{ + public sealed class Settings + { + public string EnterspeedApiUri { get; set; } = "https://management.enterspeed.com/api/v1/"; + } +} diff --git a/src/Enterspeed.Cli/Domain/Exceptions/InvalidIdFormatException.cs b/src/Enterspeed.Cli/Domain/Exceptions/InvalidIdFormatException.cs new file mode 100644 index 0000000..5e06ceb --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Exceptions/InvalidIdFormatException.cs @@ -0,0 +1,24 @@ +using System.Runtime.Serialization; + +namespace Enterspeed.Cli.Domain.Exceptions; + +[Serializable] +public class InvalidIdFormatException : Exception +{ + public InvalidIdFormatException(string type) + : base($"Invalid Id format for: {type}") + { + } + + public InvalidIdFormatException() + { + } + + public InvalidIdFormatException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidIdFormatException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) + { + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/JsonConverters/TenantIdJsonConverter.cs b/src/Enterspeed.Cli/Domain/JsonConverters/TenantIdJsonConverter.cs new file mode 100644 index 0000000..527518d --- /dev/null +++ b/src/Enterspeed.Cli/Domain/JsonConverters/TenantIdJsonConverter.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Enterspeed.Cli.Domain.Models; + +namespace Enterspeed.Cli.Domain.JsonConverters; + +public class TenantIdJsonConverter : JsonConverter +{ + public override TenantId Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write( + Utf8JsonWriter writer, + TenantId tenantIdValue, + JsonSerializerOptions options) + { + writer.WriteStringValue(tenantIdValue.IdValue); + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/DomainId.cs b/src/Enterspeed.Cli/Domain/Models/DomainId.cs new file mode 100644 index 0000000..67ddbb6 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/DomainId.cs @@ -0,0 +1,40 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class DomainId : Id +{ + public DomainId(string idValue) + : base(idValue) + { + } + + public Guid DomainGuid { get; set; } + + public static string Create() => From(Guid.NewGuid().ToString()); + + public static string From(string domainGuid) => $"{IdBase}Domain/{domainGuid}"; + + public static DomainId Parse(string domainId) + { + CheckIdValidity(domainId); + + var gidValues = GetIdValues(domainId); + + if (!gidValues.ContainsKey("Domain")) + { + throw new InvalidIdFormatException("Domain"); + } + + ValidateOrder(gidValues, "Domain"); + + var guidAsString = gidValues.FirstOrDefault(x => x.Key == "Domain").Value; + + var guid = GetValidatedGuid(guidAsString); + + return new DomainId(From(guid.ToString())) + { + DomainGuid = guid + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/EnvironmentClientId.cs b/src/Enterspeed.Cli/Domain/Models/EnvironmentClientId.cs new file mode 100644 index 0000000..50e56b0 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/EnvironmentClientId.cs @@ -0,0 +1,57 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class EnvironmentClientId : Id +{ + public EnvironmentClientId(string idValue) + : base(idValue) + { + } + + public string EnvironmentGuid { get; set; } + public string ClientGuid { get; set; } + + public static string Create(string environmentGuid) => From(environmentGuid, Guid.NewGuid().ToString()); + + public static string From(string environmentGuid, string clientGuid) => $"{IdBase}Environment/{environmentGuid}/Client/{clientGuid}"; + + public static bool TryParse(string environmentClientId, out EnvironmentClientId result) + { + try + { + result = Parse(environmentClientId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static EnvironmentClientId Parse(string environmentClientId) + { + CheckIdValidity(environmentClientId); + var gidValues = GetIdValues(environmentClientId); + + if (!gidValues.ContainsKey("Client")) + { + throw new InvalidIdFormatException("Client"); + } + + var environment = EnvironmentId.Parse(environmentClientId); + + ValidateOrder(gidValues, "Environment", "Client"); + + var guidAsString = gidValues.FirstOrDefault(x => x.Key == "Client").Value; + + var guid = GetValidatedGuid(guidAsString); + + return new EnvironmentClientId(From(environment.EnvironmentGuid, guid.ToString())) + { + EnvironmentGuid = environment.EnvironmentGuid, + ClientGuid = guid.ToString() + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/EnvironmentId.cs b/src/Enterspeed.Cli/Domain/Models/EnvironmentId.cs new file mode 100644 index 0000000..95e82ca --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/EnvironmentId.cs @@ -0,0 +1,56 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class EnvironmentId : Id +{ + public EnvironmentId(string idValue) + : base(idValue) + { + } + + public string EnvironmentGuid { get; set; } + + public static EnvironmentId Default() => Parse(From(Guid.Empty.ToString())); + + public static string Create() => From(Guid.NewGuid().ToString()); + + public static string From(string environmentGuid) => $"{IdBase}Environment/{environmentGuid}"; + + public static bool TryParse(string environmentId, out EnvironmentId result) + { + try + { + result = Parse(environmentId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static EnvironmentId Parse(string environmentId) + { + CheckIdValidity(environmentId); + + var gidValues = GetIdValues(environmentId); + + if (!gidValues.ContainsKey("Environment")) + { + throw new InvalidIdFormatException("Environment"); + } + + ValidateOrder(gidValues, "Environment"); + + var guidAsString = gidValues.FirstOrDefault(x => x.Key == "Environment").Value; + + var guid = GetValidatedGuid(guidAsString); + + return new EnvironmentId(From(guid.ToString())) + { + EnvironmentGuid = guid.ToString() + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/HostnameId.cs b/src/Enterspeed.Cli/Domain/Models/HostnameId.cs new file mode 100644 index 0000000..e94ac98 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/HostnameId.cs @@ -0,0 +1,42 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class HostnameId : Id +{ + public HostnameId(string idValue) + : base(idValue) + { + } + + public string TenantGuid { get; private set; } + public string Hostname { get; private set; } + + public static string Create(string tenantGuid, string hostname) => From(tenantGuid, hostname); + + public static string From(string tenantGuid, string hostname) => $"{IdBase}Tenant/{tenantGuid}/Hostname/{hostname}"; + + public static HostnameId Parse(string hostnameId) + { + CheckIdValidity(hostnameId); + + var gidValues = GetIdValues(hostnameId); + + if (!gidValues.ContainsKey("Hostname")) + { + throw new InvalidIdFormatException("Hostname"); + } + + ValidateOrder(gidValues, "Tenant", "Hostname"); + + var hostname = gidValues.FirstOrDefault(x => x.Key == "Hostname").Value; + + var tenant = TenantId.Parse(hostnameId); + + return new HostnameId(From(tenant.TenantGuid, hostname)) + { + TenantGuid = tenant.TenantGuid, + Hostname = hostname + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/Id.cs b/src/Enterspeed.Cli/Domain/Models/Id.cs new file mode 100644 index 0000000..d1fa0a9 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/Id.cs @@ -0,0 +1,75 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public abstract class Id : ValueObject +{ + protected Id(string idValue) => IdValue = idValue; + + public string IdValue { get; set; } + protected static string IdBase => "gid://"; + + protected static void CheckIdValidity(string id) + { + if (!(id?.StartsWith("gid://", StringComparison.Ordinal) ?? false)) + { + throw new InvalidIdFormatException("Invalid Gid"); + } + } + + protected static IDictionary GetIdValues(string idValue) + { + if (string.IsNullOrWhiteSpace(idValue)) + { + throw new InvalidIdFormatException("Null gid"); + } + + var gid = idValue.Replace("gid://", string.Empty); + + var values = gid.Split( + new[] + { + "/" + }, StringSplitOptions.RemoveEmptyEntries); + + var output = new Dictionary(); + + for (var i = 0; i < values.Length; i += 2) + { + var key = values[i]; + if (values.Length > i + 1) + { + output.Add(key, values[i + 1]); + } + } + + return output; + } + + protected static void ValidateOrder(IDictionary idValues, params string[] order) + { + var keys = idValues.Keys.Where(order.Contains).ToArray(); + + if (keys.Where((key, i) => order[i] != key).Any()) + { + throw new InvalidIdFormatException("Order"); + } + } + + protected static Guid GetValidatedGuid(string guidAsString) + { + if (!Guid.TryParse(guidAsString, out var guid)) + { + throw new InvalidIdFormatException("Guid"); + } + + return guid; + } + + public override string ToString() => IdValue; + + protected override IEnumerable GetEqualityComponents() + { + yield return IdValue; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/MappingSchemaDeploymentId.cs b/src/Enterspeed.Cli/Domain/Models/MappingSchemaDeploymentId.cs new file mode 100644 index 0000000..9d56438 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/MappingSchemaDeploymentId.cs @@ -0,0 +1,80 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class MappingSchemaDeploymentId : Id +{ + public MappingSchemaDeploymentId(string idValue) + : base(idValue) + { + } + + public string DeploymentGuid { get; set; } + + public string MappingSchemaGuid { get; set; } + + public string EnvironmentGuid { get; set; } + + public int Version { get; set; } + + public static string From( + string mappingSchemaGuid, + int mappingSchemaVersion, + string environmentGuid, + string deploymentGuid) => + $"{IdBase}MappingSchema/{mappingSchemaGuid}/Version/{mappingSchemaVersion}/Environment/{environmentGuid}/Deployment/{deploymentGuid}"; + + public static bool TryParse(string mappingSchemaDeploymentId, out MappingSchemaDeploymentId result) + { + try + { + result = Parse(mappingSchemaDeploymentId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static MappingSchemaDeploymentId Parse(string mappingSchemaDeploymentId) + { + CheckIdValidity(mappingSchemaDeploymentId); + var gidValues = GetIdValues(mappingSchemaDeploymentId); + if (!gidValues.ContainsKey("Environment")) + { + throw new InvalidIdFormatException("Environment"); + } + + ValidateOrder(gidValues, "MappingSchema", "Version", "Environment", "Deployment"); + + var mappingSchemaGuidAsString = gidValues.FirstOrDefault(x => x.Key == "MappingSchema").Value; + var mappingSchemaGuid = GetValidatedGuid(mappingSchemaGuidAsString); + + var intAsString = gidValues.FirstOrDefault(x => x.Key == "Version").Value; + if (!int.TryParse(intAsString, out var version)) + { + throw new InvalidIdFormatException("Version"); + } + + var environmentGuidAsString = gidValues.FirstOrDefault(x => x.Key == "Environment").Value; + var environmentGuid = GetValidatedGuid(environmentGuidAsString); + + var deploymentGuidAsString = gidValues.FirstOrDefault(x => x.Key == "Deployment").Value; + var deploymentGuid = GetValidatedGuid(deploymentGuidAsString); + + return new MappingSchemaDeploymentId( + From( + mappingSchemaGuid.ToString(), + version, + environmentGuid.ToString(), + deploymentGuid.ToString())) + { + MappingSchemaGuid = mappingSchemaGuid.ToString(), + EnvironmentGuid = environmentGuid.ToString(), + Version = version, + DeploymentGuid = deploymentGuid.ToString() + }; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/MappingSchemaId.cs b/src/Enterspeed.Cli/Domain/Models/MappingSchemaId.cs new file mode 100644 index 0000000..f401250 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/MappingSchemaId.cs @@ -0,0 +1,53 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class MappingSchemaId : Id +{ + public MappingSchemaId(string idValue) + : base(idValue) + { + } + + public string MappingSchemaGuid { get; set; } + + public static string Create() => From(Guid.NewGuid().ToString()); + + public static string From(string mappingSchemaGuid) => $"{IdBase}MappingSchema/{mappingSchemaGuid}"; + + public static bool TryParse(string mappingSchemaId, out MappingSchemaId result) + { + try + { + result = Parse(mappingSchemaId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static MappingSchemaId Parse(string mappingSchemaId) + { + CheckIdValidity(mappingSchemaId); + var gidValues = GetIdValues(mappingSchemaId); + + if (!gidValues.ContainsKey("MappingSchema")) + { + throw new InvalidIdFormatException("MappingSchema"); + } + + ValidateOrder(gidValues, "MappingSchema"); + + var guidAsString = gidValues.FirstOrDefault(x => x.Key == "MappingSchema").Value; + + var guid = GetValidatedGuid(guidAsString); + + return new MappingSchemaId(From(guid.ToString())) + { + MappingSchemaGuid = guid.ToString() + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/MappingSchemaVersionId.cs b/src/Enterspeed.Cli/Domain/Models/MappingSchemaVersionId.cs new file mode 100644 index 0000000..078bdad --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/MappingSchemaVersionId.cs @@ -0,0 +1,56 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class MappingSchemaVersionId : Id +{ + public MappingSchemaVersionId(string idValue) + : base(idValue) + { + } + + public string MappingSchemaGuid { get; set; } + + public int Version { get; set; } + + public static string From(string mappingSchemaGuid, int version) => $"{IdBase}MappingSchema/{mappingSchemaGuid}/Version/{version}"; + + public static bool TryParse(string mappingSchemaVersionId, out MappingSchemaVersionId result) + { + try + { + result = Parse(mappingSchemaVersionId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static MappingSchemaVersionId Parse(string mappingSchemaVersionId) + { + CheckIdValidity(mappingSchemaVersionId); + var gidValues = GetIdValues(mappingSchemaVersionId); + if (!gidValues.ContainsKey("Version")) + { + throw new InvalidIdFormatException("Version"); + } + + var mappingSchemaGid = MappingSchemaId.Parse(mappingSchemaVersionId); + ValidateOrder(gidValues, "MappingSchema", "Version"); + + var intAsString = gidValues.FirstOrDefault(x => x.Key == "Version").Value; + if (!int.TryParse(intAsString, out var version)) + { + throw new InvalidIdFormatException("Version"); + } + + return new MappingSchemaVersionId(From(mappingSchemaGid.MappingSchemaGuid, version)) + { + MappingSchemaGuid = mappingSchemaGid.MappingSchemaGuid, + Version = version + }; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/MappingStrategyId.cs b/src/Enterspeed.Cli/Domain/Models/MappingStrategyId.cs new file mode 100644 index 0000000..88269f9 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/MappingStrategyId.cs @@ -0,0 +1,62 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class MappingStrategyId : Id +{ + public MappingStrategyId(string idValue) + : base(idValue) + { + } + + public string MappingSchemaGuid { get; set; } + + public int Version { get; set; } + + public static string From(string mappingSchemaGuid, int mappingSchemaVersion) => + $"{IdBase}MappingSchema/{mappingSchemaGuid}/Version/{mappingSchemaVersion}/Strategy"; + + public static bool TryParse(string mappingStrategyId, out MappingStrategyId result) + { + try + { + result = Parse(mappingStrategyId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static MappingStrategyId Parse(string mappingStrategyId) + { + CheckIdValidity(mappingStrategyId); + if (!mappingStrategyId.EndsWith("Strategy", StringComparison.Ordinal)) + { + throw new InvalidIdFormatException("Strategy"); + } + + var gidValues = GetIdValues(mappingStrategyId); + ValidateOrder(gidValues, "MappingSchema", "Version", "Environment"); + + var mappingSchemaGuidAsString = gidValues.FirstOrDefault(x => x.Key == "MappingSchema").Value; + var mappingSchemaGuid = GetValidatedGuid(mappingSchemaGuidAsString); + + var intAsString = gidValues.FirstOrDefault(x => x.Key == "Version").Value; + if (!int.TryParse(intAsString, out var version)) + { + throw new InvalidIdFormatException("Version"); + } + + return new MappingStrategyId( + From( + mappingSchemaGuid.ToString(), + version)) + { + MappingSchemaGuid = mappingSchemaGuid.ToString(), + Version = version + }; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/PartialMappingSchemaDeploymentId.cs b/src/Enterspeed.Cli/Domain/Models/PartialMappingSchemaDeploymentId.cs new file mode 100644 index 0000000..9f899c6 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/PartialMappingSchemaDeploymentId.cs @@ -0,0 +1,68 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class PartialMappingSchemaDeploymentId : Id +{ + public PartialMappingSchemaDeploymentId(string idValue) + : base(idValue) + { + } + + public string DeploymentGuid { get; set; } + + public string PartialMappingSchemaGuid { get; set; } + + public int Version { get; set; } + + public static string From( + string partialMappingSchemaGuid, + int partialMappingSchemaVersion, + string deploymentGuid) => $"{IdBase}PartialMappingSchema/{partialMappingSchemaGuid}/Version/{partialMappingSchemaVersion}/Deployment/{deploymentGuid}"; + + public static bool TryParse( + string partialMappingSchemaDeploymentId, + out PartialMappingSchemaDeploymentId result) + { + try + { + result = Parse(partialMappingSchemaDeploymentId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static PartialMappingSchemaDeploymentId Parse(string partialMappingSchemaDeploymentId) + { + CheckIdValidity(partialMappingSchemaDeploymentId); + var gidValues = GetIdValues(partialMappingSchemaDeploymentId); + + ValidateOrder(gidValues, "PartialMappingSchema", "Version", "Deployment"); + var partialMappingSchemaGuidAsString = gidValues.FirstOrDefault(x => x.Key == "PartialMappingSchema").Value; + var partialMappingSchemaGuid = GetValidatedGuid(partialMappingSchemaGuidAsString); + + var intAsString = gidValues.FirstOrDefault(x => x.Key == "Version").Value; + if (!int.TryParse(intAsString, out var version)) + { + throw new InvalidIdFormatException("Version"); + } + + var deploymentGuidAsString = gidValues.FirstOrDefault(x => x.Key == "Deployment").Value; + var deploymentGuid = GetValidatedGuid(deploymentGuidAsString); + + return new PartialMappingSchemaDeploymentId( + From( + partialMappingSchemaGuid.ToString(), + version, + deploymentGuid.ToString())) + { + PartialMappingSchemaGuid = partialMappingSchemaGuid.ToString(), + Version = version, + DeploymentGuid = deploymentGuid.ToString() + }; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/PartialMappingSchemaId.cs b/src/Enterspeed.Cli/Domain/Models/PartialMappingSchemaId.cs new file mode 100644 index 0000000..4bb167a --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/PartialMappingSchemaId.cs @@ -0,0 +1,53 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class PartialMappingSchemaId : Id +{ + public PartialMappingSchemaId(string idValue) + : base(idValue) + { + } + + public string PartialMappingSchemaGuid { get; set; } + + public static string Create() => From(Guid.NewGuid().ToString()); + + public static string From(string partialMappingSchemaGuid) => $"{IdBase}PartialMappingSchema/{partialMappingSchemaGuid}"; + + public static bool TryParse(string partialMappingSchemaId, out PartialMappingSchemaId result) + { + try + { + result = Parse(partialMappingSchemaId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static PartialMappingSchemaId Parse(string partialMappingSchemaId) + { + CheckIdValidity(partialMappingSchemaId); + var gidValues = GetIdValues(partialMappingSchemaId); + + if (!gidValues.ContainsKey("PartialMappingSchema")) + { + throw new InvalidIdFormatException("PartialMappingSchema"); + } + + ValidateOrder(gidValues, "PartialMappingSchema"); + + var guidAsString = gidValues.FirstOrDefault(x => x.Key == "PartialMappingSchema").Value; + + var guid = GetValidatedGuid(guidAsString); + + return new PartialMappingSchemaId(From(guid.ToString())) + { + PartialMappingSchemaGuid = guid.ToString() + }; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/PartialMappingSchemaVersionId.cs b/src/Enterspeed.Cli/Domain/Models/PartialMappingSchemaVersionId.cs new file mode 100644 index 0000000..3e5fac6 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/PartialMappingSchemaVersionId.cs @@ -0,0 +1,56 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class PartialMappingSchemaVersionId : Id +{ + public PartialMappingSchemaVersionId(string idValue) + : base(idValue) + { + } + + public string PartialMappingSchemaGuid { get; set; } + + public int Version { get; set; } + + public static string From(string partialMappingSchemaId, int version) => $"{IdBase}PartialMappingSchema/{partialMappingSchemaId}/Version/{version}"; + + public static bool TryParse(string partialMappingSchemaId, out PartialMappingSchemaVersionId result) + { + try + { + result = Parse(partialMappingSchemaId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static PartialMappingSchemaVersionId Parse(string partialMappingSchemaId) + { + CheckIdValidity(partialMappingSchemaId); + var gidValues = GetIdValues(partialMappingSchemaId); + if (!gidValues.ContainsKey("Version")) + { + throw new InvalidIdFormatException("Version"); + } + + var partialMappingSchemaGid = PartialMappingSchemaId.Parse(partialMappingSchemaId); + ValidateOrder(gidValues, "PartialMappingSchema", "Version"); + + var intAsString = gidValues.FirstOrDefault(x => x.Key == "Version").Value; + if (!int.TryParse(intAsString, out var version)) + { + throw new InvalidIdFormatException("Version"); + } + + return new PartialMappingSchemaVersionId(From(partialMappingSchemaGid.PartialMappingSchemaGuid, version)) + { + PartialMappingSchemaGuid = partialMappingSchemaGid.PartialMappingSchemaGuid, + Version = version + }; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/PartialMappingStrategyId.cs b/src/Enterspeed.Cli/Domain/Models/PartialMappingStrategyId.cs new file mode 100644 index 0000000..7583fd7 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/PartialMappingStrategyId.cs @@ -0,0 +1,62 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class PartialMappingStrategyId : Id +{ + public PartialMappingStrategyId(string idValue) + : base(idValue) + { + } + + public string PartialMappingSchemaGuid { get; set; } + + public int Version { get; set; } + + public static string From(string partialMappingSchemaGuid, int partialMappingSchemaVersion) => + $"{IdBase}PartialMappingSchema/{partialMappingSchemaGuid}/Version/{partialMappingSchemaVersion}/Strategy"; + + public static bool TryParse(string partialMappingStrategyId, out PartialMappingStrategyId result) + { + try + { + result = Parse(partialMappingStrategyId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static PartialMappingStrategyId Parse(string partialMappingStrategyId) + { + CheckIdValidity(partialMappingStrategyId); + if (!partialMappingStrategyId.EndsWith("Strategy", StringComparison.Ordinal)) + { + throw new InvalidIdFormatException("Strategy"); + } + + var gidValues = GetIdValues(partialMappingStrategyId); + ValidateOrder(gidValues, "PartialMappingSchema", "Version"); + + var partialMappingSchemaGuidAsString = gidValues.FirstOrDefault(x => x.Key == "PartialMappingSchema").Value; + var partialMappingSchemaGuid = GetValidatedGuid(partialMappingSchemaGuidAsString); + + var intAsString = gidValues.FirstOrDefault(x => x.Key == "Version").Value; + if (!int.TryParse(intAsString, out var version)) + { + throw new InvalidIdFormatException("Version"); + } + + return new PartialMappingStrategyId( + From( + partialMappingSchemaGuid.ToString(), + version)) + { + PartialMappingSchemaGuid = partialMappingSchemaGuid.ToString(), + Version = version + }; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/RouteHandleId.cs b/src/Enterspeed.Cli/Domain/Models/RouteHandleId.cs new file mode 100644 index 0000000..138886a --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/RouteHandleId.cs @@ -0,0 +1,64 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class RouteHandleId : Id +{ + public RouteHandleId(string idValue) + : base(idValue) + { + } + + public string EnvironmentGuid { get; set; } + public string Handle { get; set; } + + public static string From(string environmentGuid, string handle) + { + if (string.IsNullOrWhiteSpace(handle)) + { + return null; + } + + if (string.IsNullOrWhiteSpace(environmentGuid)) + { + throw new InvalidIdFormatException("Missing environment id"); + } + + return $"{IdBase}Environment/{environmentGuid}/Route/Handle/{handle}"; + } + + public static bool TryParse(string routeId, out RouteHandleId result) + { + try + { + result = Parse(routeId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static RouteHandleId Parse(string routeId) + { + CheckIdValidity(routeId); + + var environment = EnvironmentId.Parse(routeId); + + var handles = routeId.Split($"{environment.IdValue}/Route/Handle/", StringSplitOptions.RemoveEmptyEntries); + var handle = handles.FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(handle)) + { + throw new InvalidIdFormatException("Missing handle"); + } + + return new RouteHandleId(From(environment.EnvironmentGuid, handle)) + { + EnvironmentGuid = environment.EnvironmentGuid, + Handle = handle + }; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/RouteUrlId.cs b/src/Enterspeed.Cli/Domain/Models/RouteUrlId.cs new file mode 100644 index 0000000..87cdfd2 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/RouteUrlId.cs @@ -0,0 +1,113 @@ +using System.Text.RegularExpressions; +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class RouteUrlId : Id +{ + public RouteUrlId(string idValue) + : base(idValue) + { + } + + public string EnvironmentGuid { get; set; } + public string UrlPath { get; set; } + public string DomainGuid { get; set; } + + public static string From(string environmentGuid, string domainGuid, string url) + { + if (string.IsNullOrWhiteSpace(url)) + { + return null; + } + + if (string.IsNullOrWhiteSpace(environmentGuid)) + { + throw new InvalidIdFormatException("Missing environment id"); + } + + return $"{IdBase}Environment/{environmentGuid}/Domain/{domainGuid}/Route/Url{Clean(url)}"; + } + + public static bool TryParse(string routeId, out RouteUrlId result) + { + try + { + result = Parse(routeId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static RouteUrlId Parse(string routeId) + { + CheckIdValidity(routeId); + + var environment = EnvironmentId.Parse(routeId); + + var values = GetIdValues(routeId); + + var domainGuid = values.ContainsKey("Domain") + ? values["Domain"] + : null; + + var path = routeId.Split($"{environment.IdValue}/Domain/{domainGuid}/Route/Url", StringSplitOptions.RemoveEmptyEntries); + var urlPath = path.FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(urlPath)) + { + throw new InvalidIdFormatException("Missing url"); + } + + return new RouteUrlId(From(environment.EnvironmentGuid, domainGuid, urlPath)) + { + EnvironmentGuid = environment.EnvironmentGuid, + UrlPath = urlPath, + DomainGuid = domainGuid + }; + } + + private static bool IsUrlValid(string url) => url.StartsWith("http", StringComparison.Ordinal) || url.StartsWith("/", StringComparison.Ordinal); + + private static string Clean(string url) + { + if (!IsUrlValid(url)) + { + throw new UriFormatException($"URL is invalid: {url}"); + } + + // Remove http/https + var output = Regex.Replace(url, @"^(?:http(?:s)?:\/\/)?", string.Empty, RegexOptions.IgnoreCase); + + // Remove query + if (output.Contains('?')) + { + output = output.Split('?')[0]; + } + + // Remove hashes + if (output.Contains('#')) + { + output = output.Split('#')[0]; + } + + // Remove trailing slash + if (output.EndsWith('/')) + { + output = output.TrimEnd('/'); + } + + var pathIndex = output.IndexOf("/", StringComparison.InvariantCultureIgnoreCase); + output = pathIndex == -1 + ? "/" + : output[pathIndex..]; + + output = output.ToLowerInvariant(); + + return output; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/SourceEntityId.cs b/src/Enterspeed.Cli/Domain/Models/SourceEntityId.cs new file mode 100644 index 0000000..57e6e4f --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/SourceEntityId.cs @@ -0,0 +1,56 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class SourceEntityId : Id +{ + public SourceEntityId(string idValue) + : base(idValue) + { + } + + public string SourceGuid { get; set; } + public string OriginId { get; set; } + + public static string Create(string sourceGuid, string originId) => From(sourceGuid, originId); + + public static string From(string sourceGuid, string originId) => $"{IdBase}Source/{sourceGuid}/Entity/{originId}"; + + public static bool TryParse(string sourceEntityId, out SourceEntityId result) + { + try + { + result = Parse(sourceEntityId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static SourceEntityId Parse(string sourceEntityId) + { + CheckIdValidity(sourceEntityId); + + var source = SourceId.Parse(sourceEntityId); + + var gidValues = GetIdValues(sourceEntityId); + + if (!gidValues.ContainsKey("Entity")) + { + throw new InvalidIdFormatException("Entity"); + } + + var originId = gidValues["Entity"]; + + ValidateOrder(gidValues, "Source", "Entity"); + + return new SourceEntityId(From(source.SourceGuid, originId)) + { + SourceGuid = source.SourceGuid, + OriginId = originId + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/SourceGroupId.cs b/src/Enterspeed.Cli/Domain/Models/SourceGroupId.cs new file mode 100644 index 0000000..da8d18c --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/SourceGroupId.cs @@ -0,0 +1,54 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class SourceGroupId : Id +{ + public SourceGroupId(string idValue) + : base(idValue) + { + } + + public string SourceGroupGuid { get; set; } + + public static string Create() => From(Guid.NewGuid().ToString()); + + public static string From(string sourceGroupGuid) => $"{IdBase}SourceGroup/{sourceGroupGuid}"; + + public static bool TryParse(string sourceGroupId, out SourceGroupId result) + { + try + { + result = Parse(sourceGroupId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static SourceGroupId Parse(string sourceGroupId) + { + CheckIdValidity(sourceGroupId); + + var gidValues = GetIdValues(sourceGroupId); + + if (!gidValues.ContainsKey("SourceGroup")) + { + throw new InvalidIdFormatException("SourceGroup"); + } + + ValidateOrder(gidValues, "SourceGroup"); + + var guidAsString = gidValues.FirstOrDefault(x => x.Key == "SourceGroup").Value; + + var guid = GetValidatedGuid(guidAsString); + + return new SourceGroupId(From(guid.ToString())) + { + SourceGroupGuid = guid.ToString() + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/SourceId.cs b/src/Enterspeed.Cli/Domain/Models/SourceId.cs new file mode 100644 index 0000000..b831dfc --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/SourceId.cs @@ -0,0 +1,56 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class SourceId : Id +{ + public SourceId(string idValue) + : base(idValue) + { + } + + public string SourceGuid { get; set; } + + public static SourceId Default() => Parse(From(Guid.Empty.ToString())); + + public static string Create() => From(Guid.NewGuid().ToString()); + + public static string From(string sourceGuid) => $"{IdBase}Source/{sourceGuid}"; + + public static bool TryParse(string sourceId, out SourceId result) + { + try + { + result = Parse(sourceId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static SourceId Parse(string sourceId) + { + CheckIdValidity(sourceId); + + var gidValues = GetIdValues(sourceId); + + if (!gidValues.ContainsKey("Source")) + { + throw new InvalidIdFormatException("Source"); + } + + ValidateOrder(gidValues, "Source"); + + var guidAsString = gidValues.FirstOrDefault(x => x.Key == "Source").Value; + + var guid = GetValidatedGuid(guidAsString); + + return new SourceId(From(guid.ToString())) + { + SourceGuid = guid.ToString() + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/TenantId.cs b/src/Enterspeed.Cli/Domain/Models/TenantId.cs new file mode 100644 index 0000000..77cd07c --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/TenantId.cs @@ -0,0 +1,58 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class TenantId : Id +{ + public TenantId(string idValue) + : base(idValue) + { + } + + public string TenantGuid { get; set; } + + public static string Create() => From(Guid.NewGuid().ToString()); + + public static string From(string tenantGuid) => $"{IdBase}Tenant/{tenantGuid}"; + + public static bool TryParse(string tenantId, out TenantId result) + { + try + { + result = Parse(tenantId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static TenantId Parse(string tenantId) + { + CheckIdValidity(tenantId); + var gidValues = GetIdValues(tenantId); + + if (!gidValues.ContainsKey("Tenant")) + { + throw new InvalidIdFormatException("Tenant"); + } + + ValidateOrder(gidValues, "Tenant"); + + var guidAsString = gidValues.FirstOrDefault(x => x.Key == "Tenant").Value; + + var guid = GetValidatedGuid(guidAsString); + + return new TenantId(From(guid.ToString())) + { + TenantGuid = guid.ToString() + }; + } + + public override string ToString() + { + return "Hello"; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/UserId.cs b/src/Enterspeed.Cli/Domain/Models/UserId.cs new file mode 100644 index 0000000..fc8dcfa --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/UserId.cs @@ -0,0 +1,53 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class UserId : Id +{ + public UserId(string idValue) + : base(idValue) + { + } + + public string UserGuid { get; set; } + + public static string Create() => From(Guid.NewGuid().ToString()); + + public static string From(string userGuid) => $"{IdBase}User/{userGuid}"; + + public static bool TryParse(string userId, out UserId result) + { + try + { + result = Parse(userId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static UserId Parse(string userId) + { + CheckIdValidity(userId); + var gidValues = GetIdValues(userId); + + if (!gidValues.ContainsKey("User")) + { + throw new InvalidIdFormatException("User"); + } + + ValidateOrder(gidValues, "User"); + + var guidAsString = gidValues.FirstOrDefault(x => x.Key == "User").Value; + + var guid = GetValidatedGuid(guidAsString).ToString(); + + return new UserId(From(guid)) + { + UserGuid = guid + }; + } +} diff --git a/src/Enterspeed.Cli/Domain/Models/ValueObject.cs b/src/Enterspeed.Cli/Domain/Models/ValueObject.cs new file mode 100644 index 0000000..73cc3d1 --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/ValueObject.cs @@ -0,0 +1,37 @@ +namespace Enterspeed.Cli.Domain.Models; + +public abstract class ValueObject +{ + protected static bool EqualOperator(ValueObject left, ValueObject right) + { + if (left is null ^ right is null) + { + return false; + } + return left?.Equals(right) != false; + } + + protected static bool NotEqualOperator(ValueObject left, ValueObject right) => !EqualOperator(left, right); + + protected abstract IEnumerable GetEqualityComponents(); + + public static bool operator ==(ValueObject one, ValueObject two) => EqualOperator(one, two); + + public static bool operator !=(ValueObject one, ValueObject two) => NotEqualOperator(one, two); + + public override bool Equals(object obj) + { + if (obj == null || obj.GetType() != GetType()) + { + return false; + } + + var other = (ValueObject)obj; + + return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); + } + + public override int GetHashCode() => GetEqualityComponents() + .Select(x => x != null ? x.GetHashCode() : 0) + .Aggregate((x, y) => x ^ y); +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Domain/Models/ViewId.cs b/src/Enterspeed.Cli/Domain/Models/ViewId.cs new file mode 100644 index 0000000..a169efc --- /dev/null +++ b/src/Enterspeed.Cli/Domain/Models/ViewId.cs @@ -0,0 +1,69 @@ +using Enterspeed.Cli.Domain.Exceptions; + +namespace Enterspeed.Cli.Domain.Models; + +public sealed class ViewId : Id +{ + public ViewId(string idValue) + : base(idValue) + { + } + + public string EnvironmentGuid { get; set; } + public string SourceGuid { get; set; } + public string OriginId { get; set; } + public string ViewHandle { get; set; } + + public static string Create( + string environmentGuid, + string sourceGuid, + string originId, + string viewHandle) => From(environmentGuid, sourceGuid, originId, viewHandle); + + public static string From( + string environmentGuid, + string sourceGuid, + string originId, + string viewHandle) => $"{IdBase}Environment/{environmentGuid}/Source/{sourceGuid}/Entity/{originId}/View/{viewHandle}"; + + public static bool TryParse(string viewId, out ViewId result) + { + try + { + result = Parse(viewId); + return true; + } + catch (Exception) + { + result = null; + return false; + } + } + + public static ViewId Parse(string viewId) + { + CheckIdValidity(viewId); + + var environment = EnvironmentId.Parse(viewId); + var sourceEntity = SourceEntityId.Parse(viewId); + + var gidValues = GetIdValues(viewId); + + if (!gidValues.ContainsKey("View")) + { + throw new InvalidIdFormatException("View"); + } + + var viewHandle = gidValues["View"]; + + ValidateOrder(gidValues, "Environment", "Source", "Entity", "View"); + + return new ViewId(From(environment.EnvironmentGuid, sourceEntity.SourceGuid, sourceEntity.OriginId, viewHandle)) + { + EnvironmentGuid = environment.EnvironmentGuid, + SourceGuid = sourceEntity.SourceGuid, + OriginId = sourceEntity.OriginId, + ViewHandle = viewHandle + }; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Enterspeed.Cli.csproj b/src/Enterspeed.Cli/Enterspeed.Cli.csproj new file mode 100644 index 0000000..424796c --- /dev/null +++ b/src/Enterspeed.Cli/Enterspeed.Cli.csproj @@ -0,0 +1,34 @@ + + + + Exe + net6.0 + enable + disable + Enterspeed + + + + + PreserveNewest + true + PreserveNewest + + + + + + + + + + + + + + + + + + + diff --git a/src/Enterspeed.Cli/Enterspeed.Cli.csproj.DotSettings b/src/Enterspeed.Cli/Enterspeed.Cli.csproj.DotSettings new file mode 100644 index 0000000..7bbc395 --- /dev/null +++ b/src/Enterspeed.Cli/Enterspeed.Cli.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/Enterspeed.Cli/Exceptions/ConsoleArgumentException.cs b/src/Enterspeed.Cli/Exceptions/ConsoleArgumentException.cs new file mode 100644 index 0000000..99c55d7 --- /dev/null +++ b/src/Enterspeed.Cli/Exceptions/ConsoleArgumentException.cs @@ -0,0 +1,8 @@ +namespace Enterspeed.Cli.Exceptions; + +internal class ConsoleArgumentException : Exception +{ + internal ConsoleArgumentException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Extensions/HostBuilderExtensions.cs b/src/Enterspeed.Cli/Extensions/HostBuilderExtensions.cs new file mode 100644 index 0000000..d43914d --- /dev/null +++ b/src/Enterspeed.Cli/Extensions/HostBuilderExtensions.cs @@ -0,0 +1,22 @@ +using System.CommandLine.Hosting; +using System.CommandLine.Invocation; +using Microsoft.Extensions.Hosting; + +namespace Enterspeed.Cli.Extensions; + +public static class HostBuilderExtensions +{ + public static IHostBuilder UseCommands(this IHostBuilder hostBuilder) + { + var commandHandlers = typeof(Program).Assembly.GetTypes() + .Where(t => t.GetInterfaces().Any(i => i == typeof(ICommandHandler)) + ); + + foreach (var handler in commandHandlers) + { + hostBuilder.UseCommandHandler(handler.DeclaringType, handler); + } + + return hostBuilder; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Extensions/ServiceCollectionExtensions.cs b/src/Enterspeed.Cli/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..5239cd8 --- /dev/null +++ b/src/Enterspeed.Cli/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,47 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; +using Enterspeed.Cli.Services.ConsoleOutput; +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using Enterspeed.Cli.Common.Behaviours; +using Enterspeed.Cli.Services.StateService; +using MediatR.Pipeline; +using Microsoft.Extensions.Configuration; +using Serilog; + +namespace Enterspeed.Cli.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddCli(this IServiceCollection services) + { + //services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>)); + services.AddSingleton(); + return services; + } + + public static IServiceCollection AddSerilog(this IServiceCollection services) + { + Log.Logger = CreateLogger(services); + return services; + } + + private static Serilog.Core.Logger CreateLogger(IServiceCollection services) + { + var scope = services.BuildServiceProvider(); + var loggerConfiguration = new LoggerConfiguration() + .ReadFrom.Configuration(scope.GetRequiredService()); + + loggerConfiguration.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning); + + return loggerConfiguration.CreateLogger(); + } + + public static IServiceCollection AddApplication(this IServiceCollection services) + { + services.AddMediatR(Assembly.GetExecutingAssembly()); + services.AddTransient(); + services.AddTransient(); + return services; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Program.cs b/src/Enterspeed.Cli/Program.cs new file mode 100644 index 0000000..1df1f59 --- /dev/null +++ b/src/Enterspeed.Cli/Program.cs @@ -0,0 +1,58 @@ +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Hosting; +using System.CommandLine.Parsing; +using Enterspeed.Cli.Commands.Domain; +using Enterspeed.Cli.Commands.Environment; +using Enterspeed.Cli.Commands.EnvironmentClient; +using Enterspeed.Cli.Commands.Login; +using Enterspeed.Cli.Commands.SourceEntity; +using Enterspeed.Cli.Commands.SourceGroup; +using Enterspeed.Cli.Commands.Tenant; +using Enterspeed.Cli.Commands.View; +using Enterspeed.Cli.Extensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Enterspeed.Cli; + +internal class Program +{ + internal static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args); + + public static async Task Main(string[] args) + { + var runner = BuildCommandLine() + .UseHost(_ => CreateHostBuilder(args), (builder) => builder + .ConfigureAppConfiguration((context, configuration) => + { + configuration.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true); + }) + .ConfigureServices((hostContext, services) => + { + services.AddSerilog(); + services.AddCli(); + services.AddApplication(); + }) + .UseCommands()).UseDefaults().Build(); + + return await runner.InvokeAsync(args); + } + + private static CommandLineBuilder BuildCommandLine() + { + var root = new RootCommand(); + root.AddCommand(new LoginCommand()); + root.AddCommand(TenantCommands.BuildCommands()); + root.AddCommand(EnvironmentCommands.BuildCommands()); + root.AddCommand(EnvironmentClientCommands.BuildCommands()); + root.AddCommand(DomainCommands.BuildCommands()); + root.AddCommand(SourceGroupCommands.BuildCommands()); + root.AddCommand(ViewCommands.BuildCommands()); + root.AddCommand(SourceEntityCommands.BuildCommands()); + + root.AddGlobalOption(new Option(new[] { "--output", "-o" }, "Set output to json or table") {IsHidden = true}); + + return new CommandLineBuilder(root); + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Properties/launchSettings.json b/src/Enterspeed.Cli/Properties/launchSettings.json new file mode 100644 index 0000000..f4e29d2 --- /dev/null +++ b/src/Enterspeed.Cli/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Enterspeed.Cli": { + "commandName": "Project", + "commandLineArgs": "tenant set 0d62b9cf-f8dc-4778-9c3b-5ee8d2470a57" + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/ConsoleOutput/IOutputService.cs b/src/Enterspeed.Cli/Services/ConsoleOutput/IOutputService.cs new file mode 100644 index 0000000..a21fdef --- /dev/null +++ b/src/Enterspeed.Cli/Services/ConsoleOutput/IOutputService.cs @@ -0,0 +1,6 @@ +namespace Enterspeed.Cli.Services.ConsoleOutput; + +public interface IOutputService +{ + void Write(T value, OutputStyle style = OutputStyle.Json); +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/ConsoleOutput/OutputService.cs b/src/Enterspeed.Cli/Services/ConsoleOutput/OutputService.cs new file mode 100644 index 0000000..a3fac38 --- /dev/null +++ b/src/Enterspeed.Cli/Services/ConsoleOutput/OutputService.cs @@ -0,0 +1,49 @@ +using System.Text.Json; +using Enterspeed.Cli.Domain; +using Enterspeed.Cli.Domain.JsonConverters; + +namespace Enterspeed.Cli.Services.ConsoleOutput; + +public class OutputService : IOutputService +{ + public OutputService() + { + + } + + public void Write(T value, OutputStyle style = OutputStyle.Json) + { + switch (style) + { + case OutputStyle.Json: + WriteJson(value); + return; + case OutputStyle.Table: + WriteTable(value); + return; + } + } + + private void WriteJson(T value) + { + if (value == null) + { + return; + } + + var jsonSerializerOptions = new JsonSerializerOptions + { + WriteIndented = true + }; + + jsonSerializerOptions.Converters.Add(new TenantIdJsonConverter()); + + var jsonString = JsonSerializer.Serialize(value, jsonSerializerOptions); + Console.WriteLine(jsonString); + } + + private void WriteTable(T value) + { + Console.WriteLine("TABLE output not implemented yet"); + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/ConsoleOutput/OutputStyle.cs b/src/Enterspeed.Cli/Services/ConsoleOutput/OutputStyle.cs new file mode 100644 index 0000000..4589bea --- /dev/null +++ b/src/Enterspeed.Cli/Services/ConsoleOutput/OutputStyle.cs @@ -0,0 +1,4 @@ +public enum OutputStyle { + Json, + Table, +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/EnterspeedClient/EnterspeedClient.cs b/src/Enterspeed.Cli/Services/EnterspeedClient/EnterspeedClient.cs new file mode 100644 index 0000000..56bfbfb --- /dev/null +++ b/src/Enterspeed.Cli/Services/EnterspeedClient/EnterspeedClient.cs @@ -0,0 +1,106 @@ +using System.Net; +using Enterspeed.Cli.Api.Identity.Models; +using Enterspeed.Cli.Configuration; +using Enterspeed.Cli.Services.StateService; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using RestSharp; + +namespace Enterspeed.Cli.Services.EnterspeedClient; + +public class EnterspeedClient : IEnterspeedClient, IDisposable +{ + private readonly ILogger _logger; + private readonly IStateService _stateService; + private readonly RestClient _client; + + public EnterspeedClient(ILogger logger, IConfiguration configuration, IStateService stateService) + { + _logger = logger; + _stateService = stateService; + var settings = configuration.GetRequiredSection("Settings").Get(); + + var options = new RestClientOptions(settings.EnterspeedApiUri); + + _client = new RestClient(options) + { + }; + } + + public async Task ExecuteAsync(RestRequest request, CancellationToken cancellationToken = default) + { + if (_stateService.Token == null) + { + _logger.LogError("No token found. Please run 'es-cli login' first"); + return default; + } + + var tenantId = _stateService.ActiveTenant(); + _logger.LogInformation($"TenantId: {tenantId.IdValue}"); + + request.AddHeader("Authorization", $"Bearer {_stateService.Token.AccessToken}"); + request.AddHeader("X-Tenant-Id", tenantId.IdValue); + + var response = await _client.ExecuteAsync(request, cancellationToken); + + // If Unauthorized, refresh token and try again + if (response.StatusCode == HttpStatusCode.Unauthorized) + { + _logger.LogInformation("Unauthorized, trying to refresh token"); + var refreshResult = await RefreshToken(_stateService.Token.RefreshToken); + + if (refreshResult.IsValid) + { + request.AddOrUpdateHeader("Authorization", $"Bearer {_stateService.Token.AccessToken}"); + response = await _client.ExecuteAsync(request, cancellationToken); + if (response.StatusCode == HttpStatusCode.Unauthorized) + { + _logger.LogError("Unauthorized! You need to run 'es-cli login' again"); + return default; + } + } + } + + if (!response.IsSuccessful) + { + _logger.LogError($"Unsuccessful: {response.StatusCode}"); + } + + return response.Data; + } + + public async Task Authenticate(AuthenticationRequest request, string redirectUrl = null) + { + var authRequest = new RestRequest("identity/authenticate", Method.Post) + .AddJsonBody(request); + + if (redirectUrl != null) + { + authRequest.AddQueryParameter("redirect_uri", redirectUrl); + } + var response = await _client.ExecuteAsync(authRequest); + + if (response.IsSuccessful && response.Data != null) + { + _stateService.SaveState(response.Data.Token, response.Data.User); + } + + return response.Data; + } + + private async Task RefreshToken(string refreshToken) + { + return await Authenticate( + new AuthenticationRequest + { + Type = "refresh_token", + Token = refreshToken + }); + } + + public void Dispose() + { + _client?.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/EnterspeedClient/IEnterspeedClient.cs b/src/Enterspeed.Cli/Services/EnterspeedClient/IEnterspeedClient.cs new file mode 100644 index 0000000..214874d --- /dev/null +++ b/src/Enterspeed.Cli/Services/EnterspeedClient/IEnterspeedClient.cs @@ -0,0 +1,10 @@ +using Enterspeed.Cli.Api.Identity.Models; +using RestSharp; + +namespace Enterspeed.Cli.Services.EnterspeedClient; + +public interface IEnterspeedClient +{ + public Task ExecuteAsync(RestRequest request, CancellationToken cancellationToken = default); + public Task Authenticate(AuthenticationRequest request, string redirectUrl = null); +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/StateService/IStateService.cs b/src/Enterspeed.Cli/Services/StateService/IStateService.cs new file mode 100644 index 0000000..88474f3 --- /dev/null +++ b/src/Enterspeed.Cli/Services/StateService/IStateService.cs @@ -0,0 +1,13 @@ +using Enterspeed.Cli.Api.Identity.Models; +using Enterspeed.Cli.Domain.Models; + +namespace Enterspeed.Cli.Services.StateService; + +public interface IStateService +{ + Token Token { get; } + IdentityUser User { get; } + void SaveState(Token token, IdentityUser user); + void SetActiveTenant(TenantId tenantId); + TenantId ActiveTenant(); +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/StateService/StateService.cs b/src/Enterspeed.Cli/Services/StateService/StateService.cs new file mode 100644 index 0000000..0b2585b --- /dev/null +++ b/src/Enterspeed.Cli/Services/StateService/StateService.cs @@ -0,0 +1,89 @@ +using System.Text.Json; +using Enterspeed.Cli.Api.Identity.Models; +using Enterspeed.Cli.Domain.Models; +using Microsoft.Extensions.Logging; + +namespace Enterspeed.Cli.Services.StateService; + +public class StateService : IStateService +{ + private readonly ILogger _logger; + private const string Filename = ".escli-state.json"; + + public StateService(ILogger logger) + { + _logger = logger; + LoadState(); + } + + public IdentityUser User { get; private set; } + + public Token Token { get; private set; } + + private TenantId ActiveTenantId { get; set; } + + public void SaveState(Token token, IdentityUser user) + { + User = user; + Token = token; + + if (ActiveTenantId == null || !User.Tenants.Keys.Contains(ActiveTenantId.IdValue)) + { + ActiveTenantId = TenantId.Parse(User.Tenants.FirstOrDefault().Key); + } + + var jsonString = JsonSerializer.Serialize(new State + { + Token = token, + User = user, + ActiveTenantId = ActiveTenantId.IdValue + }); + File.WriteAllText(Filename, jsonString); + _logger.LogInformation("State saved"); + } + + public void SetActiveTenant(TenantId tenantId) + { + _logger.LogInformation($"Setting active tenant to: {tenantId.IdValue}"); + ActiveTenantId = tenantId; + SaveState(Token, User); + } + + public TenantId ActiveTenant() + { + if (ActiveTenantId == null) + { + ActiveTenantId = TenantId.Parse(User.Tenants.Keys.FirstOrDefault()); + } + return ActiveTenantId; + } + + private void LoadState() + { + try + { + var jsonString = File.ReadAllText(Filename); + var state = JsonSerializer.Deserialize(jsonString); + if (state != null) + { + Token = state.Token; + User = state.User; + if (state.ActiveTenantId != null) + { + ActiveTenantId = TenantId.Parse(state.ActiveTenantId); + } + } + } + catch (Exception) + { + _logger.LogWarning("No state file found."); + } + } +} + +public class State +{ + public Token Token { get; set; } + public IdentityUser User { get; set; } + public string ActiveTenantId { get; set; } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/appsettings.Development.json b/src/Enterspeed.Cli/appsettings.Development.json new file mode 100644 index 0000000..29872e8 --- /dev/null +++ b/src/Enterspeed.Cli/appsettings.Development.json @@ -0,0 +1,5 @@ +{ + "Settings": { + "EnterspeedApiUri": "https://management.dev.enterspeed.io/api/v1/" + } +} diff --git a/src/Enterspeed.Cli/appsettings.json b/src/Enterspeed.Cli/appsettings.json new file mode 100644 index 0000000..bdb5c0a --- /dev/null +++ b/src/Enterspeed.Cli/appsettings.json @@ -0,0 +1,42 @@ +{ + "Settings": { + "EnterspeedApiUri": "https://management.dev.enterspeed.io/api/v1/" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Warning" + } + }, + "Serilog": { + "Using": [ + "Serilog", + "Serilog.Sinks.Console" + ], + "MinimumLevel": { + "Default": "Verbose", + "Override": { + "System": "Warning", + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Async", + "Args": { + "configure": [ + { + "Name": "Console", + "Args": { + "restrictedToMinimumLevel": "Debug", + "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3} {SourceContext}] {Message}{NewLine}{Exception}" + } + } + ] + } + } + ], + "Enrich": [ "WithExceptionDetails" ] + } +} \ No newline at end of file