diff --git a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/App/AppSynchronizer.cs b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/App/AppSynchronizer.cs index ea4c5fa8..d8b16dc5 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/App/AppSynchronizer.cs +++ b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/App/AppSynchronizer.cs @@ -100,7 +100,7 @@ public Task DescribeAsync(ISyncService sync, MarkdownWriter writer) writer.H3("Clients"); writer.Paragraph($"{rows.Length} client(s)."); - writer.Table(new[] { "Name", "Label", "Role" }, rows); + writer.Table(["Name", "Label", "Role"], rows); } if (model.Roles.Count > 0) @@ -109,7 +109,7 @@ public Task DescribeAsync(ISyncService sync, MarkdownWriter writer) writer.H3("Roles"); writer.Paragraph($"{rows.Length} role(s)."); - writer.Table(new[] { "Name", "Permissions" }, rows); + writer.Table(["Name", "Permissions"], rows); } if (model.Contributors.Count > 0) @@ -118,7 +118,7 @@ public Task DescribeAsync(ISyncService sync, MarkdownWriter writer) writer.H3("Contributors"); writer.Paragraph($"{rows.Length} contributor(s)."); - writer.Table(new[] { "Id", "Role" }, rows); + writer.Table(["Id", "Role"], rows); } if (model.Languages.Count > 0) @@ -127,7 +127,7 @@ public Task DescribeAsync(ISyncService sync, MarkdownWriter writer) writer.H3("Languages"); writer.Paragraph($"{rows.Length} language(s)."); - writer.Table(new[] { "Code", "Master" }, rows); + writer.Table(["Code", "Master"], rows); } return Task.CompletedTask; diff --git a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Contents/ContentsSynchronizer.cs b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Contents/ContentsSynchronizer.cs index ae428fdf..aff3cd56 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Contents/ContentsSynchronizer.cs +++ b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Contents/ContentsSynchronizer.cs @@ -84,6 +84,7 @@ await client.GetAllAsync(async content => return; } + // Convert schema IDs to schema names. content.MapComponents(schemaMap); contents.Add(content.ToModel(schema.Name)); @@ -120,7 +121,7 @@ public Task DescribeAsync(ISyncService sync, MarkdownWriter writer) if (rows.Length > 0) { - writer.Table(new[] { "Schema", "Counts" }, rows); + writer.Table(["Schema", "Counts"], rows); } return Task.CompletedTask; @@ -153,7 +154,7 @@ public async Task ImportAsync(ISyncService sync, SyncOptions options, ISession s DoNotScript = true, DoNotValidate = false, DoNotValidateWorkflow = true, - Jobs = model.Contents.Select(x => x.ToUpsert(schemas, options.ContentAction)).ToList() + Jobs = model.Contents.Select(x => x.ToUpsert(schemas, schemaMap, options.ContentAction)).ToList() }; var contentIdAssigned = false; diff --git a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Contents/Extensions.cs b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Contents/Extensions.cs index 9965064d..4e275cd2 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Contents/Extensions.cs +++ b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Contents/Extensions.cs @@ -13,7 +13,10 @@ namespace Squidex.CLI.Commands.Implementation.Sync.Contents; internal static class Extensions { - public static BulkUpdateJob ToUpsert(this ContentModel model, SchemasDto schemas, ContentAction action) + public static BulkUpdateJob ToUpsert(this ContentModel model, + SchemasDto schemas, + Dictionary schemasMap, + ContentAction action) { var result = SimpleMapper.Map(model, new BulkUpdateJob()); @@ -25,6 +28,9 @@ public static BulkUpdateJob ToUpsert(this ContentModel model, SchemasDto schemas result.Id = singleton.Id; } + // Convert schema names back to IDs. + model.MapComponents(schemasMap); + switch (action) { case ContentAction.UpsertPatch: @@ -42,7 +48,6 @@ public static BulkUpdateJob ToUpsert(this ContentModel model, SchemasDto schemas } result.Data = model.Data; - return result; } diff --git a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Rules/RulesSynchronizer.cs b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Rules/RulesSynchronizer.cs index 391afe6d..a3065d7f 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Rules/RulesSynchronizer.cs +++ b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Rules/RulesSynchronizer.cs @@ -70,7 +70,7 @@ public Task DescribeAsync(ISyncService sync, MarkdownWriter writer) { var rows = models.Select(x => new object[] { x.Name, x.Trigger.TypeName(), x.Action.TypeName() }).OrderBy(x => x[0]).ToArray(); - writer.Table(new[] { "Name", "Trigger", "Action" }, rows); + writer.Table(["Name", "Trigger", "Action"], rows); } return Task.CompletedTask; diff --git a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Schemas/SchemasSynchronizer.cs b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Schemas/SchemasSynchronizer.cs index 3eb1922b..ba36f5fb 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Schemas/SchemasSynchronizer.cs +++ b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Schemas/SchemasSynchronizer.cs @@ -52,7 +52,7 @@ public Task DescribeAsync(ISyncService sync, MarkdownWriter writer) { var rows = models.Select(x => new object[] { x.Name, x.SchemaType.ToString(), x.Schema.Fields.Count }).OrderBy(x => x[0]).ToArray(); - writer.Table(new[] { "Schema", "Type", "Fields" }, rows); + writer.Table(["Schema", "Type", "Fields"], rows); } return Task.CompletedTask; diff --git a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Workflows/WorkflowsSynchronizer.cs b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Workflows/WorkflowsSynchronizer.cs index 6fa4f1f1..794b810a 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Workflows/WorkflowsSynchronizer.cs +++ b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/Sync/Workflows/WorkflowsSynchronizer.cs @@ -68,7 +68,7 @@ public Task DescribeAsync(ISyncService sync, MarkdownWriter writer) { var rows = models.Select(x => new object[] { x.Name, string.Join(", ", x.Steps.Select(y => y.Key)), x.Initial }).OrderBy(x => x[0]).ToArray(); - writer.Table(new[] { "Name", "Steps", "Initial" }, rows); + writer.Table(["Name", "Steps", "Initial"], rows); } return Task.CompletedTask; diff --git a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/TestData/LoremIpsum.cs b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/TestData/LoremIpsum.cs index 1d217fb6..22d020f7 100644 --- a/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/TestData/LoremIpsum.cs +++ b/cli/Squidex.CLI/Squidex.CLI.Core/Commands/Implementation/TestData/LoremIpsum.cs @@ -14,7 +14,7 @@ public static class LoremIpsum private const int SentencesPerParagraph = 3; private static readonly string[] Words = - { + [ "lorem", "ipsum", "dolor", @@ -35,7 +35,7 @@ public static class LoremIpsum "magna", "aliquam", "erat" - }; + ]; public static string GetWord(Random random) { diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs index 54cc6c4e..d7464955 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Assets.cs @@ -58,7 +58,7 @@ public async Task Import(ImportArguments arguments) log.WriteLine($"Uploading: {file.FullName}"); - if (existings.Items.Any(x => string.Equals(x.FileHash, fileHash, StringComparison.Ordinal))) + if (existings.Items.Exists(x => string.Equals(x.FileHash, fileHash, StringComparison.Ordinal))) { log.StepSkipped("Same hash."); } diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Config.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Config.cs index 9735715d..07da5963 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Config.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Config.cs @@ -48,11 +48,11 @@ public void List(ListArguments arguments) [Command("add", Description = "Add or update an app.")] public void Add(AddArguments arguments) { - var entry = !string.IsNullOrWhiteSpace(arguments.Label) ? arguments.Label : arguments.Name; + var entry = !string.IsNullOrWhiteSpace(arguments.Label) ? arguments.Label : arguments.App; var appConfig = new ConfiguredApp { - Name = arguments.Name, + Name = arguments.App, ClientId = arguments.ClientId, ClientSecret = arguments.ClientSecret, IgnoreSelfSigned = arguments.IgnoreSelfSigned, @@ -63,7 +63,7 @@ public void Add(AddArguments arguments) if (arguments.Use) { - configuration.UseApp(arguments.Name); + configuration.UseApp(arguments.App); log.Completed("App added and selected."); } @@ -76,7 +76,7 @@ public void Add(AddArguments arguments) [Command("use", Description = "Use an app.")] public void Use(UseArguments arguments) { - configuration.UseApp(arguments.Name); + configuration.UseApp(arguments.App); log.Completed("App selected."); } @@ -84,7 +84,7 @@ public void Use(UseArguments arguments) [Command("remove", Description = "Remove an app.")] public void Remove(RemoveArguments arguments) { - configuration.Remove(arguments.Name); + configuration.Remove(arguments.App); log.Completed("App removed."); } @@ -109,36 +109,36 @@ public sealed class Validator : AbstractValidator public sealed class RemoveArguments : IArgumentModel { - [Operand("name", Description = "The name of the app.")] - public string Name { get; set; } + [Operand("app", Description = "The name of the app.")] + public string App { get; set; } public sealed class Validator : AbstractValidator { public Validator() { - RuleFor(x => x.Name).NotEmpty(); + RuleFor(x => x.App).NotEmpty(); } } } public sealed class UseArguments : IArgumentModel { - [Operand("name", Description = "The name of the app.")] - public string Name { get; set; } + [Operand("app", Description = "The name of the app.")] + public string App { get; set; } public sealed class Validator : AbstractValidator { public Validator() { - RuleFor(x => x.Name).NotEmpty(); + RuleFor(x => x.App).NotEmpty(); } } } public sealed class AddArguments : IArgumentModel { - [Operand("name", Description = "The name of the app.")] - public string Name { get; set; } + [Operand("app", Description = "The name of the app.")] + public string App { get; set; } [Operand("client-id", Description = "The client id.")] public string ClientId { get; set; } @@ -165,7 +165,7 @@ public sealed class Validator : AbstractValidator { public Validator() { - RuleFor(x => x.Name).NotEmpty(); + RuleFor(x => x.App).NotEmpty(); RuleFor(x => x.ClientId).NotEmpty(); RuleFor(x => x.ClientSecret).NotEmpty(); } diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Indexes.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Indexes.cs new file mode 100644 index 00000000..fc5d703e --- /dev/null +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Indexes.cs @@ -0,0 +1,170 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Text; +using System.Text.RegularExpressions; +using CommandDotNet; +using ConsoleTables; +using FluentValidation; +using Squidex.CLI.Commands.Implementation; +using Squidex.CLI.Configuration; +using Squidex.ClientLibrary; + +namespace Squidex.CLI.Commands; + +#pragma warning disable MA0048 // File name must match type name + +public sealed partial class App +{ + [Command("indexes", Description = "Manage schemas.")] + [Subcommand] + public sealed partial class Indexes(IConfigurationService configuration, ILogger log) + { + [Command("list", Description = "List all schemas.")] + public async Task List(ListArguments arguments) + { + var session = configuration.StartSession(arguments.App); + + var indexes = await session.Client.Schemas.GetIndexesAsync(arguments.Schema); + + if (arguments.Table) + { + var table = new ConsoleTable("Name", "Fields"); + + foreach (var index in indexes.Items) + { + var fields = new StringBuilder(); + + foreach (var field in index.Fields) + { + fields.AppendLine($"{field.Name}: {field.Order}"); + } + + table.AddRow(index.Name, fields.ToString()); + } + + log.WriteLine(table.ToString()); + } + else + { + log.WriteLine(indexes.JsonPrettyString()); + } + } + + [Command("create", Description = "Create a new index.")] + public async Task Create(CreateArguments arguments) + { + var session = configuration.StartSession(arguments.App); + + var indexRequest = new CreateIndexDto(); + var indexMatch = FieldsRegex().Match(arguments.Fields); + + var groups = indexMatch.Groups.OfType(); + + var fieldNames = groups.Where(x => x.Name == "Field").ToList(); + var fieldOrders = groups.Where(x => x.Name == "Order").ToList(); + + static SortOrder ParseOrder(string order) + { + if (string.Equals(order, "asc", StringComparison.OrdinalIgnoreCase) || + string.Equals(order, "ascending", StringComparison.OrdinalIgnoreCase)) + { + return SortOrder.Ascending; + } + + return SortOrder.Descending; + } + + for (var i = 0; i < fieldNames.Count; i++) + { + var order = ParseOrder(fieldOrders[i].Value); + + indexRequest.Fields ??= []; + indexRequest.Fields.Add(new IndexFieldDto { Name = fieldNames[i].Value, Order = order }); + } + + if (arguments.Print) + { + log.WriteLine(indexRequest.JsonPrettyString()); + return; + } + + await session.Client.Schemas.PostIndexAsync(arguments.Schema, indexRequest); + + log.Completed("Index creation scheduled. Will be created in the background."); + } + + [Command("drop", Description = "Create a new index.")] + public async Task Drop(DropArguments arguments) + { + var session = configuration.StartSession(arguments.App); + + await session.Client.Schemas.DeleteIndexAsync(arguments.Schema, arguments.Index); + + log.Completed("Index creation scheduled. Will be created in the background."); + } + + public sealed class ListArguments : AppArguments + { + [Operand("name", Description = "The name of the schema.")] + public string Schema { get; set; } + + [Option('t', "table", Description = "Output as table.")] + public bool Table { get; set; } + + public sealed class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.Schema).NotEmpty(); + } + } + } + + public sealed class CreateArguments : AppArguments + { + [Operand("name", Description = "The name of the schema.")] + public string Schema { get; set; } + + [Operand("fields", Description = "The fields in the following format 'field1=ASC,field2=DESC'")] + public string Fields { get; set; } + + [Option("print", Description = "Only print the request to see the fields to be created.")] + public bool Print { get; set; } + + public sealed class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.Schema).NotEmpty(); + RuleFor(x => x.Fields).NotEmpty().Matches(FieldsRegex()); + } + } + } + + public sealed class DropArguments : AppArguments + { + [Operand("name", Description = "The name of the schema.")] + public string Schema { get; set; } + + [Operand("index", Description = "The name of the index.")] + public string Index { get; set; } + + public sealed class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.Schema).NotEmpty(); + RuleFor(x => x.Index).NotEmpty(); + } + } + } + + [GeneratedRegex("((?[a-z0-9-._]+)[\\s]*=[\\s]*(?ASC|DESC|ASCENDING|DESC)[\\s\\,\\;]*)", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture)] + private static partial Regex FieldsRegex(); + } +} diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Log.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Log.cs index c7b30b8f..4ace9de8 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Log.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Log.cs @@ -14,7 +14,6 @@ using Squidex.CLI.Commands.Implementation; #pragma warning disable MA0048 // File name must match type name -#pragma warning disable RECS0014 // If all fields, properties and methods members are static, the class can be made static. namespace Squidex.CLI.Commands; diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_OpenLibrary.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_OpenLibrary.cs index 8c95bebe..57a4ff2d 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/App_OpenLibrary.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_OpenLibrary.cs @@ -29,10 +29,10 @@ public async Task New(NewArguments arguments) await synchronizer.ImportAsync("assembly://Squidex.CLI.Commands.Implementation.OpenLibrary.Structure", new SyncOptions { - Targets = new[] - { + Targets = + [ "schemas" - }, + ], Recreate = true }, session); diff --git a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Schemas.cs b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Schemas.cs index 9190c326..3ead5af2 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Commands/App_Schemas.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Commands/App_Schemas.cs @@ -54,7 +54,7 @@ public async Task Get(GetArguments arguments) { var session = configuration.StartSession(arguments.App); - var schema = await session.Client.Schemas.GetSchemaAsync(arguments.Name); + var schema = await session.Client.Schemas.GetSchemaAsync(arguments.Schema); if (arguments.WithReferencedNames) { @@ -76,8 +76,7 @@ public async Task Sync(SyncArguments arguments) var session = configuration.StartSession(arguments.App); var schemaText = string.Empty; - var schemaName = arguments.Name; - + var schemaName = arguments.Schema; try { schemaText = await File.ReadAllTextAsync(arguments.File); @@ -120,7 +119,7 @@ public async Task Sync(SyncArguments arguments) { var request = SchemaWithRefs.Parse(schemaText); - if (!arguments.NoRefFix && request.ReferencedSchemas.Any()) + if (!arguments.NoRefFix && request.ReferencedSchemas.Count != 0) { var allSchemas = await session.Client.Schemas.GetSchemasAsync(); @@ -137,7 +136,7 @@ public async Task Sync(SyncArguments arguments) { var request = SchemaWithRefs.Parse(schemaText); - if (!arguments.NoRefFix && request.ReferencedSchemas.Any()) + if (!arguments.NoRefFix && request.ReferencedSchemas.Count != 0) { var allSchemas = await session.Client.Schemas.GetSchemasAsync(); @@ -165,8 +164,8 @@ public sealed class Validator : AbstractValidator public sealed class GetArguments : AppArguments { - [Operand("name", Description = "The name of the schema.")] - public string Name { get; set; } + [Operand("schema", Description = "The name of the schema.")] + public string Schema { get; set; } [Option('r', "with-refs", Description = "Includes the names of the referenced schemas.")] public bool WithReferencedNames { get; set; } @@ -175,7 +174,7 @@ public sealed class Validator : AbstractValidator { public Validator() { - RuleFor(x => x.Name).NotEmpty(); + RuleFor(x => x.Schema).NotEmpty(); } } } @@ -185,8 +184,8 @@ public sealed class SyncArguments : AppArguments [Operand("file", Description = "The file with the schema json.")] public string File { get; set; } - [Option("name", Description = "The new schema name.")] - public string Name { get; set; } + [Option("schema", Description = "The new schema name.")] + public string Schema { get; set; } [Option("no-delete", Description = "Do not delete fields.")] public bool NoFieldDeletion { get; set; } diff --git a/cli/Squidex.CLI/Squidex.CLI/Program.cs b/cli/Squidex.CLI/Squidex.CLI/Program.cs index cbfb1d4a..4fec1b57 100644 --- a/cli/Squidex.CLI/Squidex.CLI/Program.cs +++ b/cli/Squidex.CLI/Squidex.CLI/Program.cs @@ -38,6 +38,7 @@ public static int Main(string[] args) .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton()