Browse Source

Merge pull request #103 from Squidex/feature-regex-suggestions

Feature regex suggestions
pull/104/head
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
936a6b5f73
  1. 10
      src/Squidex.Shared/Identity/SquidexRoles.cs
  2. 2
      src/Squidex/Config/Constants.cs
  3. 19
      src/Squidex/Config/MyUIOptions.cs
  4. 49
      src/Squidex/Config/Swagger/ScopesProcessor.cs
  5. 5
      src/Squidex/Config/Swagger/SwaggerServices.cs
  6. 2
      src/Squidex/Controllers/Api/Apps/AppClientsController.cs
  7. 2
      src/Squidex/Controllers/Api/Apps/AppContributorsController.cs
  8. 2
      src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs
  9. 2
      src/Squidex/Controllers/Api/Apps/AppsController.cs
  10. 2
      src/Squidex/Controllers/Api/Apps/Models/AddAppLanguageDto.cs
  11. 2
      src/Squidex/Controllers/Api/Apps/Models/AppLanguageDto.cs
  12. 2
      src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs
  13. 2
      src/Squidex/Controllers/Api/Apps/Models/ContributorsDto.cs
  14. 2
      src/Squidex/Controllers/Api/Apps/Models/UpdateAppClientDto.cs
  15. 2
      src/Squidex/Controllers/Api/Apps/Models/UpdateAppLanguageDto.cs
  16. 2
      src/Squidex/Controllers/Api/Assets/AssetContentController.cs
  17. 2
      src/Squidex/Controllers/Api/Assets/AssetsController.cs
  18. 2
      src/Squidex/Controllers/Api/Assets/Models/AssetUpdateDto.cs
  19. 2
      src/Squidex/Controllers/Api/EntityCreatedDto.cs
  20. 2
      src/Squidex/Controllers/Api/History/HistoryController.cs
  21. 2
      src/Squidex/Controllers/Api/History/Models/HistoryEventDto.cs
  22. 2
      src/Squidex/Controllers/Api/LanguageDto.cs
  23. 2
      src/Squidex/Controllers/Api/Languages/LanguagesController.cs
  24. 2
      src/Squidex/Controllers/Api/Ping/PingController.cs
  25. 2
      src/Squidex/Controllers/Api/Plans/AppPlansController.cs
  26. 2
      src/Squidex/Controllers/Api/Plans/Models/AppPlansDto.cs
  27. 2
      src/Squidex/Controllers/Api/Plans/Models/ChangePlanDto.cs
  28. 2
      src/Squidex/Controllers/Api/Plans/Models/PlanChangedDto.cs
  29. 2
      src/Squidex/Controllers/Api/Plans/Models/PlanDto.cs
  30. 2
      src/Squidex/Controllers/Api/Schemas/Models/ReorderFieldsDto.cs
  31. 2
      src/Squidex/Controllers/Api/Schemas/Models/SchemaDto.cs
  32. 2
      src/Squidex/Controllers/Api/Schemas/Models/SchemaPropertiesDto.cs
  33. 2
      src/Squidex/Controllers/Api/Schemas/Models/UpdateFieldDto.cs
  34. 2
      src/Squidex/Controllers/Api/Schemas/Models/UpdateSchemaDto.cs
  35. 2
      src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs
  36. 2
      src/Squidex/Controllers/Api/Schemas/SchemasController.cs
  37. 2
      src/Squidex/Controllers/Api/Statistics/Models/CallsUsageDto.cs
  38. 2
      src/Squidex/Controllers/Api/Statistics/Models/CurrentCallsDto.cs
  39. 2
      src/Squidex/Controllers/Api/Statistics/Models/CurrentStorageDto.cs
  40. 2
      src/Squidex/Controllers/Api/Statistics/Models/StorageUsageDto.cs
  41. 2
      src/Squidex/Controllers/Api/Statistics/UsagesController.cs
  42. 27
      src/Squidex/Controllers/Api/UI/Models/UIRegexSuggestionDto.cs
  43. 22
      src/Squidex/Controllers/Api/UI/Models/UISettingsDto.cs
  44. 57
      src/Squidex/Controllers/Api/UI/UIController.cs
  45. 2
      src/Squidex/Controllers/Api/Users/Models/UserCreatedDto.cs
  46. 2
      src/Squidex/Controllers/Api/Users/Models/UsersDto.cs
  47. 2
      src/Squidex/Controllers/Api/Users/UserManagementController.cs
  48. 2
      src/Squidex/Controllers/Api/Users/UsersController.cs
  49. 2
      src/Squidex/Controllers/Api/Webhooks/Models/CreateWebhookDto.cs
  50. 2
      src/Squidex/Controllers/Api/Webhooks/Models/UpdateWebhookDto.cs
  51. 2
      src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs
  52. 2
      src/Squidex/Controllers/Api/Webhooks/Models/WebhookDto.cs
  53. 2
      src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventDto.cs
  54. 2
      src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventsDto.cs
  55. 2
      src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs
  56. 2
      src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs
  57. 2
      src/Squidex/Controllers/ContentApi/ContentSwaggerController.cs
  58. 2
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  59. 56
      src/Squidex/Controllers/ContentApi/Generator/SchemaSwaggerGenerator.cs
  60. 26
      src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs
  61. 2
      src/Squidex/Controllers/ContentApi/Models/ContentsDto.cs
  62. 2
      src/Squidex/Controllers/UI/Error/ErrorController.cs
  63. 2
      src/Squidex/Controllers/UI/Profile/PortalController.cs
  64. 2
      src/Squidex/Controllers/UI/Profile/ProfileController.cs
  65. 7
      src/Squidex/Pipeline/Swagger/SwaggerHelper.cs
  66. 2
      src/Squidex/Startup.cs
  67. 15
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html
  68. 27
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.scss
  69. 45
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts
  70. 1
      src/Squidex/app/shared/declarations-base.ts
  71. 2
      src/Squidex/app/shared/module.ts
  72. 79
      src/Squidex/app/shared/services/ui.service.spec.ts
  73. 49
      src/Squidex/app/shared/services/ui.service.ts
  74. 16
      src/Squidex/appsettings.json

10
src/Squidex.Shared/Identity/SquidexRoles.cs

@ -10,14 +10,14 @@ namespace Squidex.Shared.Identity
{
public static class SquidexRoles
{
public static readonly string Administrator = "ADMINISTRATOR";
public static readonly string Administrator = "administrator";
public static readonly string AppOwner = "APP-OWNER";
public static readonly string AppOwner = "app:owner";
public static readonly string AppEditor = "APP-EDITOR";
public static readonly string AppEditor = "app:editor";
public static readonly string AppReader = "APP-READER";
public static readonly string AppReader = "app:reader";
public static readonly string AppDeveloper = "APP-DEVELOPER";
public static readonly string AppDeveloper = "app:dev";
}
}

2
src/Squidex/Config/Constants.cs

@ -10,7 +10,7 @@ namespace Squidex.Config
{
public static class Constants
{
public static readonly string SecurityDefinition = "oauth-client-auth";
public static readonly string SecurityDefinition = "squidex-oauth-auth";
public static readonly string ApiPrefix = "/api";

19
src/Squidex/Config/MyUIOptions.cs

@ -0,0 +1,19 @@
// ==========================================================================
// MyUIOptions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
// ReSharper disable CollectionNeverUpdated.Global
namespace Squidex.Config
{
public sealed class MyUIOptions
{
public Dictionary<string, string> RegexSuggestions { get; set; }
}
}

49
src/Squidex/Config/Swagger/ScopesProcessor.cs

@ -0,0 +1,49 @@
// ==========================================================================
// ScopesProcessor.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using NSwag;
using NSwag.SwaggerGeneration.Processors;
using NSwag.SwaggerGeneration.Processors.Contexts;
using Squidex.Infrastructure.Tasks;
// ReSharper disable InvertIf
namespace Squidex.Config.Swagger
{
public class ScopesProcessor : IOperationProcessor
{
public Task<bool> ProcessAsync(OperationProcessorContext context)
{
if (context.OperationDescription.Operation.Security == null)
{
context.OperationDescription.Operation.Security = new List<SwaggerSecurityRequirement>();
}
var authorizeAttributes =
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Union(
context.MethodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<AuthorizeAttribute>()).ToArray();
if (authorizeAttributes.Any())
{
var scopes = authorizeAttributes.Where(a => a.Roles != null).SelectMany(a => a.Roles.Split(',')).Distinct().ToList();
context.OperationDescription.Operation.Security.Add(new SwaggerSecurityRequirement
{
{ Constants.SecurityDefinition, scopes }
});
}
return TaskHelper.True;
}
}
}

5
src/Squidex/Config/Swagger/SwaggerServices.cs

@ -42,10 +42,9 @@ namespace Squidex.Config.Swagger
private static SwaggerSettings ConfigureIdentity(this SwaggerSettings settings, MyUrlsOptions urlOptions)
{
settings.DocumentProcessors.Add(
new SecurityDefinitionAppender(Constants.SecurityDefinition, SwaggerHelper.CreateOAuthSchema(urlOptions)));
settings.DocumentProcessors.Add(new SecurityDefinitionAppender(Constants.SecurityDefinition, SwaggerHelper.CreateOAuthSchema(urlOptions)));
settings.OperationProcessors.Add(new OperationSecurityScopeProcessor(Constants.SecurityDefinition));
settings.OperationProcessors.Add(new ScopesProcessor());
return settings;
}

2
src/Squidex/Controllers/Api/Apps/AppClientsController.cs

@ -26,7 +26,7 @@ namespace Squidex.Controllers.Api.Apps
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Apps")]
public class AppClientsController : ControllerBase
public sealed class AppClientsController : ControllerBase
{
public AppClientsController(ICommandBus commandBus)
: base(commandBus)

2
src/Squidex/Controllers/Api/Apps/AppContributorsController.cs

@ -27,7 +27,7 @@ namespace Squidex.Controllers.Api.Apps
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Apps")]
public class AppContributorsController : ControllerBase
public sealed class AppContributorsController : ControllerBase
{
private readonly IAppPlansProvider appPlansProvider;

2
src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs

@ -29,7 +29,7 @@ namespace Squidex.Controllers.Api.Apps
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Apps")]
public class AppLanguagesController : ControllerBase
public sealed class AppLanguagesController : ControllerBase
{
public AppLanguagesController(ICommandBus commandBus)
: base(commandBus)

2
src/Squidex/Controllers/Api/Apps/AppsController.cs

@ -28,7 +28,7 @@ namespace Squidex.Controllers.Api.Apps
[Authorize]
[ApiExceptionFilter]
[SwaggerTag("Apps")]
public class AppsController : ControllerBase
public sealed class AppsController : ControllerBase
{
private readonly IAppRepository appRepository;

2
src/Squidex/Controllers/Api/Apps/Models/AddAppLanguageDto.cs

@ -11,7 +11,7 @@ using Squidex.Infrastructure;
namespace Squidex.Controllers.Api.Apps.Models
{
public class AddAppLanguageDto
public sealed class AddAppLanguageDto
{
/// <summary>
/// The language to add.

2
src/Squidex/Controllers/Api/Apps/Models/AppLanguageDto.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Controllers.Api.Apps.Models
{
public class AppLanguageDto
public sealed class AppLanguageDto
{
/// <summary>
/// The iso code of the language.

2
src/Squidex/Controllers/Api/Apps/Models/ClientDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Apps.Models
{
public class ClientDto
public sealed class ClientDto
{
/// <summary>
/// The client id.

2
src/Squidex/Controllers/Api/Apps/Models/ContributorsDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Apps.Models
{
public class ContributorsDto
public sealed class ContributorsDto
{
/// <summary>
/// The contributors.

2
src/Squidex/Controllers/Api/Apps/Models/UpdateAppClientDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Apps.Models
{
public class UpdateAppClientDto
public sealed class UpdateAppClientDto
{
/// <summary>
/// The new display name of the client.

2
src/Squidex/Controllers/Api/Apps/Models/UpdateAppLanguageDto.cs

@ -11,7 +11,7 @@ using Squidex.Infrastructure;
namespace Squidex.Controllers.Api.Apps.Models
{
public class UpdateAppLanguageDto
public sealed class UpdateAppLanguageDto
{
/// <summary>
/// Set the value to true to make the language the master.

2
src/Squidex/Controllers/Api/Assets/AssetContentController.cs

@ -26,7 +26,7 @@ namespace Squidex.Controllers.Api.Assets
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Assets")]
public class AssetContentController : ControllerBase
public sealed class AssetContentController : ControllerBase
{
private readonly IAssetStore assetStorage;
private readonly IAssetRepository assetRepository;

2
src/Squidex/Controllers/Api/Assets/AssetsController.cs

@ -36,7 +36,7 @@ namespace Squidex.Controllers.Api.Assets
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Assets")]
public class AssetsController : ControllerBase
public sealed class AssetsController : ControllerBase
{
private readonly IAssetRepository assetRepository;
private readonly IAssetStatsRepository assetStatsRepository;

2
src/Squidex/Controllers/Api/Assets/Models/AssetUpdateDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Assets.Models
{
public class AssetUpdateDto
public sealed class AssetUpdateDto
{
/// <summary>
/// The new name of the asset.

2
src/Squidex/Controllers/Api/EntityCreatedDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api
{
public class EntityCreatedDto
public sealed class EntityCreatedDto
{
/// <summary>
/// Id of the created entity.

2
src/Squidex/Controllers/Api/History/HistoryController.cs

@ -25,7 +25,7 @@ namespace Squidex.Controllers.Api.History
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("History")]
public class HistoryController : ControllerBase
public sealed class HistoryController : ControllerBase
{
private readonly IHistoryEventRepository historyEventRepository;

2
src/Squidex/Controllers/Api/History/Models/HistoryEventDto.cs

@ -12,7 +12,7 @@ using NodaTime;
namespace Squidex.Controllers.Api.History.Models
{
public class HistoryEventDto
public sealed class HistoryEventDto
{
/// <summary>
/// The message of the event.

2
src/Squidex/Controllers/Api/LanguageDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api
{
public class LanguageDto
public sealed class LanguageDto
{
/// <summary>
/// The iso code of the language.

2
src/Squidex/Controllers/Api/Languages/LanguagesController.cs

@ -22,7 +22,7 @@ namespace Squidex.Controllers.Api.Languages
[Authorize]
[ApiExceptionFilter]
[SwaggerTag("Languages")]
public class LanguagesController : Controller
public sealed class LanguagesController : Controller
{
/// <summary>
/// Get supported languages.

2
src/Squidex/Controllers/Api/Ping/PingController.cs

@ -19,7 +19,7 @@ namespace Squidex.Controllers.Api.Ping
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Ping")]
public class PingController : Controller
public sealed class PingController : Controller
{
/// <summary>
/// Get ping status.

2
src/Squidex/Controllers/Api/Plans/AppPlansController.cs

@ -28,7 +28,7 @@ namespace Squidex.Controllers.Api.Plans
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Plans")]
public class AppPlansController : ControllerBase
public sealed class AppPlansController : ControllerBase
{
private readonly IAppPlansProvider appPlansProvider;
private readonly IAppPlanBillingManager appPlansBillingManager;

2
src/Squidex/Controllers/Api/Plans/Models/AppPlansDto.cs

@ -10,7 +10,7 @@ using System.Collections.Generic;
namespace Squidex.Controllers.Api.Plans.Models
{
public class AppPlansDto
public sealed class AppPlansDto
{
/// <summary>
/// The available plans.

2
src/Squidex/Controllers/Api/Plans/Models/ChangePlanDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Plans.Models
{
public class ChangePlanDto
public sealed class ChangePlanDto
{
/// <summary>
/// The new plan id.

2
src/Squidex/Controllers/Api/Plans/Models/PlanChangedDto.cs

@ -8,7 +8,7 @@
namespace Squidex.Controllers.Api.Plans.Models
{
public class PlanChangedDto
public sealed class PlanChangedDto
{
/// <summary>
/// Optional redirect uri.

2
src/Squidex/Controllers/Api/Plans/Models/PlanDto.cs

@ -8,7 +8,7 @@
namespace Squidex.Controllers.Api.Plans.Models
{
public class PlanDto
public sealed class PlanDto
{
/// <summary>
/// The id of the plan.

2
src/Squidex/Controllers/Api/Schemas/Models/ReorderFieldsDto.cs

@ -11,7 +11,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Schemas.Models
{
public class ReorderFieldsDto
public sealed class ReorderFieldsDto
{
/// <summary>
/// The field ids in the target order.

2
src/Squidex/Controllers/Api/Schemas/Models/SchemaDto.cs

@ -13,7 +13,7 @@ using Squidex.Infrastructure;
namespace Squidex.Controllers.Api.Schemas.Models
{
public class SchemaDto
public sealed class SchemaDto
{
/// <summary>
/// The id of the schema.

2
src/Squidex/Controllers/Api/Schemas/Models/SchemaPropertiesDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Schemas.Models
{
public class SchemaPropertiesDto
public sealed class SchemaPropertiesDto
{
/// <summary>
/// Optional label for the editor.

2
src/Squidex/Controllers/Api/Schemas/Models/UpdateFieldDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Schemas.Models
{
public class UpdateFieldDto
public sealed class UpdateFieldDto
{
/// <summary>
/// The field properties.

2
src/Squidex/Controllers/Api/Schemas/Models/UpdateSchemaDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Schemas.Models
{
public class UpdateSchemaDto
public sealed class UpdateSchemaDto
{
/// <summary>
/// Optional label for the editor.

2
src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs

@ -23,7 +23,7 @@ namespace Squidex.Controllers.Api.Schemas
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Schemas")]
public class SchemaFieldsController : ControllerBase
public sealed class SchemaFieldsController : ControllerBase
{
public SchemaFieldsController(ICommandBus commandBus)
: base(commandBus)

2
src/Squidex/Controllers/Api/Schemas/SchemasController.cs

@ -30,7 +30,7 @@ namespace Squidex.Controllers.Api.Schemas
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Schemas")]
public class SchemasController : ControllerBase
public sealed class SchemasController : ControllerBase
{
private readonly ISchemaRepository schemaRepository;

2
src/Squidex/Controllers/Api/Statistics/Models/CallsUsageDto.cs

@ -10,7 +10,7 @@ using System;
namespace Squidex.Controllers.Api.Statistics.Models
{
public class CallsUsageDto
public sealed class CallsUsageDto
{
/// <summary>
/// The date when the usage was tracked.

2
src/Squidex/Controllers/Api/Statistics/Models/CurrentCallsDto.cs

@ -8,7 +8,7 @@
namespace Squidex.Controllers.Api.Statistics.Models
{
public class CurrentCallsDto
public sealed class CurrentCallsDto
{
/// <summary>
/// The number of calls.

2
src/Squidex/Controllers/Api/Statistics/Models/CurrentStorageDto.cs

@ -8,7 +8,7 @@
namespace Squidex.Controllers.Api.Statistics.Models
{
public class CurrentStorageDto
public sealed class CurrentStorageDto
{
/// <summary>
/// The size in bytes.

2
src/Squidex/Controllers/Api/Statistics/Models/StorageUsageDto.cs

@ -10,7 +10,7 @@ using System;
namespace Squidex.Controllers.Api.Statistics.Models
{
public class StorageUsageDto
public sealed class StorageUsageDto
{
/// <summary>
/// The date when the usage was tracked.

2
src/Squidex/Controllers/Api/Statistics/UsagesController.cs

@ -27,7 +27,7 @@ namespace Squidex.Controllers.Api.Statistics
[ApiExceptionFilter]
[AppApi]
[SwaggerTag("Statistics")]
public class UsagesController : ControllerBase
public sealed class UsagesController : ControllerBase
{
private readonly IUsageTracker usageTracker;
private readonly IAppPlansProvider appPlanProvider;

27
src/Squidex/Controllers/Api/UI/Models/UIRegexSuggestionDto.cs

@ -0,0 +1,27 @@
// ==========================================================================
// UIRegexSuggestionDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.UI.Models
{
public sealed class UIRegexSuggestionDto
{
/// <summary>
/// The name of the suggestion.
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// The regex pattern.
/// </summary>
[Required]
public string Pattern { get; set; }
}
}

22
src/Squidex/Controllers/Api/UI/Models/UISettingsDto.cs

@ -0,0 +1,22 @@
// ==========================================================================
// UISettingsDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.UI.Models
{
public sealed class UISettingsDto
{
/// <summary>
/// The regex suggestions.
/// </summary>
[Required]
public List<UIRegexSuggestionDto> RegexSuggestions { get; set; }
}
}

57
src/Squidex/Controllers/Api/UI/UIController.cs

@ -0,0 +1,57 @@
// ==========================================================================
// UIController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NSwag.Annotations;
using Squidex.Config;
using Squidex.Controllers.Api.UI.Models;
using Squidex.Pipeline;
namespace Squidex.Controllers.Api.UI
{
/// <summary>
/// Manages ui settings and configs.
/// </summary>
[ApiExceptionFilter]
[SwaggerTag("UI")]
public sealed class UIController : Controller
{
private readonly MyUIOptions uiOptions;
public UIController(IOptions<MyUIOptions> uiOptions)
{
this.uiOptions = uiOptions.Value;
}
/// <summary>
/// Get ui settings.
/// </summary>
[HttpGet]
[Route("ui/settings/")]
[ProducesResponseType(typeof(UISettingsDto), 200)]
[ApiCosts(0)]
public IActionResult GetSettings()
{
var dto = new UISettingsDto
{
RegexSuggestions =
uiOptions.RegexSuggestions?
.Where(x =>
!string.IsNullOrWhiteSpace(x.Key) &&
!string.IsNullOrWhiteSpace(x.Value))
.Select(x => new UIRegexSuggestionDto { Name = x.Key, Pattern = x.Value }).ToList()
?? new List<UIRegexSuggestionDto>()
};
return Ok(dto);
}
}
}

2
src/Squidex/Controllers/Api/Users/Models/UserCreatedDto.cs

@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Users.Models
{
public class UserCreatedDto
public sealed class UserCreatedDto
{
[Required]
public string Id { get; set; }

2
src/Squidex/Controllers/Api/Users/Models/UsersDto.cs

@ -8,7 +8,7 @@
namespace Squidex.Controllers.Api.Users.Models
{
public class UsersDto
public sealed class UsersDto
{
/// <summary>
/// The total number of users.

2
src/Squidex/Controllers/Api/Users/UserManagementController.cs

@ -25,7 +25,7 @@ namespace Squidex.Controllers.Api.Users
[MustBeAdministrator]
[ApiExceptionFilter]
[SwaggerIgnore]
public class UserManagementController : Controller
public sealed class UserManagementController : Controller
{
private readonly UserManager<IUser> userManager;
private readonly IUserFactory userFactory;

2
src/Squidex/Controllers/Api/Users/UsersController.cs

@ -30,7 +30,7 @@ namespace Squidex.Controllers.Api.Users
/// </summary>
[ApiExceptionFilter]
[SwaggerTag("Users")]
public class UsersController : Controller
public sealed class UsersController : Controller
{
private static readonly byte[] AvatarBytes;
private readonly UserManager<IUser> userManager;

2
src/Squidex/Controllers/Api/Webhooks/Models/CreateWebhookDto.cs

@ -14,7 +14,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Webhooks.Models
{
public class CreateWebhookDto
public sealed class CreateWebhookDto
{
/// <summary>
/// The url of the webhook.

2
src/Squidex/Controllers/Api/Webhooks/Models/UpdateWebhookDto.cs

@ -12,7 +12,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Webhooks.Models
{
public class UpdateWebhookDto
public sealed class UpdateWebhookDto
{
/// <summary>
/// The url of the webhook.

2
src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs

@ -11,7 +11,7 @@ using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.Api.Webhooks.Models
{
public class WebhookCreatedDto
public sealed class WebhookCreatedDto
{
/// <summary>
/// The id of the webhook.

2
src/Squidex/Controllers/Api/Webhooks/Models/WebhookDto.cs

@ -14,7 +14,7 @@ using System.Collections.Generic;
namespace Squidex.Controllers.Api.Webhooks.Models
{
public class WebhookDto
public sealed class WebhookDto
{
/// <summary>
/// The id of the webhook.

2
src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventDto.cs

@ -13,7 +13,7 @@ using Squidex.Domain.Apps.Read.Webhooks;
namespace Squidex.Controllers.Api.Webhooks.Models
{
public class WebhookEventDto
public sealed class WebhookEventDto
{
/// <summary>
/// The id of the event.

2
src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventsDto.cs

@ -8,7 +8,7 @@
namespace Squidex.Controllers.Api.Webhooks.Models
{
public class WebhookEventsDto
public sealed class WebhookEventsDto
{
/// <summary>
/// The total number of webhook events.

2
src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs

@ -10,7 +10,7 @@ using System;
namespace Squidex.Controllers.Api.Webhooks.Models
{
public class WebhookSchemaDto
public sealed class WebhookSchemaDto
{
/// <summary>
/// The id of the schema.

2
src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs

@ -29,7 +29,7 @@ namespace Squidex.Controllers.Api.Webhooks
[AppApi]
[SwaggerTag("Webhooks")]
[MustBeAppDeveloper]
public class WebhooksController : ControllerBase
public sealed class WebhooksController : ControllerBase
{
private readonly IWebhookRepository webhooksRepository;
private readonly IWebhookEventRepository webhookEventsRepository;

2
src/Squidex/Controllers/ContentApi/ContentSwaggerController.cs

@ -20,7 +20,7 @@ namespace Squidex.Controllers.ContentApi
{
[ApiExceptionFilter]
[SwaggerIgnore]
public class ContentSwaggerController : Controller
public sealed class ContentSwaggerController : Controller
{
private readonly ISchemaRepository schemaRepository;
private readonly IAppProvider appProvider;

2
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -32,7 +32,7 @@ namespace Squidex.Controllers.ContentApi
[ApiExceptionFilter]
[AppApi]
[SwaggerIgnore]
public class ContentsController : ControllerBase
public sealed class ContentsController : ControllerBase
{
private readonly ISchemaProvider schemas;
private readonly IContentRepository contentRepository;

56
src/Squidex/Controllers/ContentApi/Generator/SchemaSwaggerGenerator.cs

@ -11,11 +11,13 @@ using System.Collections.Generic;
using System.Linq;
using NJsonSchema;
using NSwag;
using Squidex.Config;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Contents.JsonSchema;
using Squidex.Infrastructure;
using Squidex.Pipeline.Swagger;
using Squidex.Shared.Identity;
// ReSharper disable InvertIf
@ -23,8 +25,10 @@ namespace Squidex.Controllers.ContentApi.Generator
{
public sealed class SchemaSwaggerGenerator
{
private static readonly string schemaQueryDescription;
private static readonly string schemaBodyDescription;
private static readonly string SchemaQueryDescription;
private static readonly string SchemaBodyDescription;
private static readonly List<SwaggerSecurityRequirement> EditorSecurity;
private static readonly List<SwaggerSecurityRequirement> ReaderSecurity;
private readonly ContentSchemaBuilder schemaBuilder = new ContentSchemaBuilder();
private readonly SwaggerDocument document;
private readonly JsonSchema4 contentSchema;
@ -36,8 +40,28 @@ namespace Squidex.Controllers.ContentApi.Generator
static SchemaSwaggerGenerator()
{
schemaBodyDescription = SwaggerHelper.LoadDocs("schemabody");
schemaQueryDescription = SwaggerHelper.LoadDocs("schemaquery");
SchemaBodyDescription = SwaggerHelper.LoadDocs("schemabody");
SchemaQueryDescription = SwaggerHelper.LoadDocs("schemaquery");
ReaderSecurity = new List<SwaggerSecurityRequirement>
{
new SwaggerSecurityRequirement
{
{
Constants.SecurityDefinition, new[] { SquidexRoles.AppReader }
}
}
};
EditorSecurity = new List<SwaggerSecurityRequirement>
{
new SwaggerSecurityRequirement
{
{
Constants.SecurityDefinition, new[] { SquidexRoles.AppEditor }
}
}
};
}
public SchemaSwaggerGenerator(SwaggerDocument document, string path, Schema schema,
@ -89,7 +113,7 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.OperationId = $"Query{schemaKey}Contents";
operation.Summary = $"Queries {schemaName} contents.";
operation.Description = schemaQueryDescription;
operation.Description = SchemaQueryDescription;
operation.AddQueryParameter("$top", JsonObjectType.Number, "Optional number of contents to take.");
operation.AddQueryParameter("$skip", JsonObjectType.Number, "Optional number of contents to skip.");
@ -98,6 +122,8 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.AddQueryParameter("orderby", JsonObjectType.String, "Optional OData order definition.");
operation.AddResponse("200", $"{schemaName} content retrieved.", CreateContentsSchema(schemaName, contentSchema));
operation.Security = ReaderSecurity;
});
}
@ -109,6 +135,8 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.Summary = $"Get a {schemaName} content.";
operation.AddResponse("200", $"{schemaName} content found.", contentSchema);
operation.Security = ReaderSecurity;
});
}
@ -119,10 +147,12 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.OperationId = $"Create{schemaKey}Content";
operation.Summary = $"Create a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, schemaBodyDescription);
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription);
operation.AddQueryParameter("publish", JsonObjectType.Boolean, "Set to true to autopublish content.");
operation.AddResponse("201", $"{schemaName} created.", contentSchema);
operation.Security = EditorSecurity;
});
}
@ -133,9 +163,11 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.OperationId = $"Update{schemaKey}Content";
operation.Summary = $"Update a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, schemaBodyDescription);
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription);
operation.AddResponse("204", $"{schemaName} element updated.");
operation.Security = EditorSecurity;
});
}
@ -146,9 +178,11 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.OperationId = $"Path{schemaKey}Content";
operation.Summary = $"Patchs a {schemaName} content.";
operation.AddBodyParameter("data", contentSchema, schemaBodyDescription);
operation.AddBodyParameter("data", contentSchema, SchemaBodyDescription);
operation.AddResponse("204", $"{schemaName} element updated.");
operation.Security = EditorSecurity;
});
}
@ -160,6 +194,8 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.Summary = $"Publish a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} element published.");
operation.Security = EditorSecurity;
});
}
@ -171,6 +207,8 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.Summary = $"Unpublish a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} element unpublished.");
operation.Security = EditorSecurity;
});
}
@ -182,6 +220,8 @@ namespace Squidex.Controllers.ContentApi.Generator
operation.Summary = $"Delete a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} content deleted.");
operation.Security = EditorSecurity;
});
}

26
src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

@ -20,7 +20,6 @@ using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
using Squidex.Pipeline.Swagger;
using Squidex.Shared.Identity;
// ReSharper disable InvertIf
// ReSharper disable SuggestBaseTypeForParameter
@ -55,37 +54,12 @@ namespace Squidex.Controllers.ContentApi.Generator
swaggerGenerator = new SwaggerGenerator(schemaGenerator, settings, schemaResolver);
GenerateSchemasOperations(schemas, app);
GenerateSecurityRequirements();
await GenerateDefaultErrorsAsync();
return document;
}
private void GenerateSecurityRequirements()
{
var securityRequirements = new List<SwaggerSecurityRequirement>
{
new SwaggerSecurityRequirement
{
{
Constants.SecurityDefinition,
new List<string>
{
SquidexRoles.AppOwner,
SquidexRoles.AppDeveloper,
SquidexRoles.AppEditor
}
}
}
};
foreach (var operation in document.Paths.Values.SelectMany(x => x.Values))
{
operation.Security = securityRequirements;
}
}
private void GenerateSchemasOperations(IEnumerable<ISchemaEntity> schemas, IAppEntity app)
{
var appBasePath = $"/content/{app.Name}";

2
src/Squidex/Controllers/ContentApi/Models/ContentsDto.cs

@ -8,7 +8,7 @@
namespace Squidex.Controllers.ContentApi.Models
{
public class AssetsDto
public sealed class AssetsDto
{
/// <summary>
/// The total number of content items.

2
src/Squidex/Controllers/UI/Error/ErrorController.cs

@ -12,7 +12,7 @@ using NSwag.Annotations;
namespace Squidex.Controllers.UI.Error
{
[SwaggerIgnore]
public class ErrorController : Controller
public sealed class ErrorController : Controller
{
[Route("error")]
public IActionResult Error()

2
src/Squidex/Controllers/UI/Profile/PortalController.cs

@ -17,7 +17,7 @@ namespace Squidex.Controllers.UI.Profile
{
[Authorize]
[SwaggerIgnore]
public class PortalController : Controller
public sealed class PortalController : Controller
{
private readonly IAppPlanBillingManager appPlansBillingManager;

2
src/Squidex/Controllers/UI/Profile/ProfileController.cs

@ -27,7 +27,7 @@ namespace Squidex.Controllers.UI.Profile
{
[Authorize]
[SwaggerIgnore]
public class ProfileController : Controller
public sealed class ProfileController : Controller
{
private readonly SignInManager<IUser> signInManager;
private readonly UserManager<IUser> userManager;

7
src/Squidex/Pipeline/Swagger/SwaggerHelper.cs

@ -94,9 +94,10 @@ namespace Squidex.Pipeline.Swagger
Scopes = new Dictionary<string, string>
{
{ Constants.ApiScope, "Read and write access to the API" },
{ SquidexRoles.AppOwner, "You get this scope / role when you are owner of the app you are accessing." },
{ SquidexRoles.AppEditor, "You get this scope / role when you are owner of the app you are accessing or when the subject is a client." },
{ SquidexRoles.AppDeveloper, "You get this scope / role when you are owner of the app you are accessing." }
{ SquidexRoles.AppOwner, "App contributor with Owner permission." },
{ SquidexRoles.AppEditor, "Client (writer) or App contributor with Editor permission." },
{ SquidexRoles.AppReader, "Client (readonly) or App contributor with Editor permission." },
{ SquidexRoles.AppDeveloper, "App contributor with Developer permission." },
},
Description = securityDescription
};

2
src/Squidex/Startup.cs

@ -76,6 +76,8 @@ namespace Squidex
Configuration.GetSection("urls"));
services.Configure<MyIdentityOptions>(
Configuration.GetSection("identity"));
services.Configure<MyUIOptions>(
Configuration.GetSection("ui"));
services.Configure<MyUsageOptions>(
Configuration.GetSection("usage"));

15
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html

@ -24,7 +24,20 @@
<label class="col col-3 col-form-label" for="field-pattern">Pattern</label>
<div class="col col-6">
<input type="text" class="form-control" id="field-pattern" formControlName="pattern" placeholder="Regex Pattern" />
<input type="text" class="form-control" id="field-pattern" formControlName="pattern" placeholder="Regex Pattern" #patternInput
autocomplete="off"
autocorrect="off"
autocapitalize="off"
(focus)="regexSuggestionsModal.show()" (blur)="regexSuggestionsModal.hide()" />
<div *ngIf="regexSuggestions.length > 0 && (regexSuggestionsModal.isOpen | async) && (showPatternSuggestions | async)" [sqxModalTarget]="patternInput" class="control-dropdown">
<h4>Suggestions</h4>
<div *ngFor="let suggestion of regexSuggestions" class="control-dropdown-item control-dropdown-item-selectable" (mousedown)="setPattern(suggestion.pattern)">
<div class="truncate">{{suggestion.name}}</div>
<div class="truncate text-muted">{{suggestion.pattern}}</div>
</div>
</div>
</div>
</div>
<div class="form-group row" *ngIf="showPatternMessage | async">

27
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.scss

@ -11,6 +11,33 @@
}
}
.control-dropdown {
& {
max-width: 285px;
min-height: 220px;
}
h4 {
padding: .5rem 0 0 .5rem;
}
&-item {
& {
font-size: .85rem;
}
&:hover {
.text-muted {
color: $color-dark-foreground !important;
}
}
}
}
.form-check-input {
margin: 0;
}
.truncate {
@include truncate;
}

45
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts

@ -9,7 +9,12 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable, Subscription } from 'rxjs';
import { StringFieldPropertiesDto } from 'shared';
import {
ModalView,
StringFieldPropertiesDto,
UIRegexSuggestionDto,
UIService
} from 'shared';
@Component({
selector: 'sqx-string-validation',
@ -18,6 +23,7 @@ import { StringFieldPropertiesDto } from 'shared';
})
export class StringValidationComponent implements OnDestroy, OnInit {
private patternSubscription: Subscription;
private uiSettingsSubscription: Subscription;
@Input()
public editForm: FormGroup;
@ -27,9 +33,20 @@ export class StringValidationComponent implements OnDestroy, OnInit {
public showDefaultValue: Observable<boolean>;
public showPatternMessage: Observable<boolean>;
public showPatternSuggestions: Observable<boolean>;
public regexSuggestions: UIRegexSuggestionDto[] = [];
public regexSuggestionsModal = new ModalView(false, false);
constructor(
private readonly uiService: UIService
) {
}
public ngOnDestroy() {
this.patternSubscription.unsubscribe();
this.uiSettingsSubscription.unsubscribe();
}
public ngOnInit() {
@ -58,11 +75,27 @@ export class StringValidationComponent implements OnDestroy, OnInit {
.startWith('')
.map(x => x && x.trim().length > 0);
this.showPatternSuggestions =
this.editForm.controls['pattern'].valueChanges
.startWith('')
.map(x => !x || x.trim().length === 0);
this.uiSettingsSubscription =
this.uiService.getSettings()
.subscribe(settings => {
this.regexSuggestions = settings.regexSuggestions;
});
this.patternSubscription =
this.editForm.controls['pattern'].valueChanges.subscribe((value: string) => {
if (!value || value.length === 0) {
this.editForm.controls['patternMessage'].setValue(undefined);
}
});
this.editForm.controls['pattern'].valueChanges
.subscribe((value: string) => {
if (!value || value.length === 0) {
this.editForm.controls['patternMessage'].setValue(undefined);
}
});
}
public setPattern(pattern: string) {
this.editForm.controls['pattern'].setValue(pattern);
}
}

1
src/Squidex/app/shared/declarations-base.ts

@ -31,6 +31,7 @@ export * from './services/history.service';
export * from './services/languages.service';
export * from './services/plans.service';
export * from './services/schemas.service';
export * from './services/ui.service';
export * from './services/usages.service';
export * from './services/users-provider.service';
export * from './services/users.service';

2
src/Squidex/app/shared/module.ts

@ -44,6 +44,7 @@ import {
ResolveSchemaGuard,
SchemasService,
ResolveUserGuard,
UIService,
UsagesService,
UserDtoPicture,
UserEmailPipe,
@ -129,6 +130,7 @@ export class SqxSharedModule {
ResolveSchemaGuard,
ResolveUserGuard,
SchemasService,
UIService,
UsagesService,
UserManagementService,
UsersProviderService,

79
src/Squidex/app/shared/services/ui.service.spec.ts

@ -0,0 +1,79 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import {
ApiUrlConfig,
UIService,
UISettingsDto
} from './../';
describe('UIService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [
UIService,
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }
]
});
});
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
httpMock.verify();
}));
it('should make get request to get settings',
inject([UIService, HttpTestingController], (uiService: UIService, httpMock: HttpTestingController) => {
let settings1: UISettingsDto | null = null;
let settings2: UISettingsDto | null = null;
uiService.getSettings().subscribe(result => {
settings1 = result;
});
const response: UISettingsDto = { regexSuggestions: [] };
const req = httpMock.expectOne('http://service/p/api/ui/settings');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();
req.flush(response);
uiService.getSettings().subscribe(result => {
settings2 = result;
});
expect(settings1).toEqual(response);
expect(settings2).toEqual(response);
}));
it('should return default settings when error occurs',
inject([UIService, HttpTestingController], (uiService: UIService, httpMock: HttpTestingController) => {
let settings: UISettingsDto | null = null;
uiService.getSettings().subscribe(result => {
settings = result;
});
const req = httpMock.expectOne('http://service/p/api/ui/settings');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();
req.error(new ErrorEvent('500'));
expect(settings.regexSuggestions).toEqual([]);
}));
});

49
src/Squidex/app/shared/services/ui.service.ts

@ -0,0 +1,49 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import 'framework/angular/http-extensions';
import { ApiUrlConfig } from 'framework';
export interface UISettingsDto {
regexSuggestions: UIRegexSuggestionDto[];
}
export interface UIRegexSuggestionDto {
name: string; pattern: string;
}
@Injectable()
export class UIService {
private settings: UISettingsDto;
constructor(
private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig
) {
}
public getSettings(): Observable<UISettingsDto> {
if (this.settings) {
return Observable.of(this.settings);
} else {
const url = this.apiUrl.buildUrl(`api/ui/settings`);
return this.http.get<UISettingsDto>(url)
.catch(error => {
return Observable.of({ regexSuggestions: [] });
})
.do(settings => {
this.settings = settings;
});
}
}
}

16
src/Squidex/appsettings.json

@ -6,6 +6,22 @@
"baseUrl": "http://localhost:5000"
},
"ui": {
/*
* Regex suggestions for the UI
*/
"regexSuggestions": {
// Regex for emails.
"Email": "^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\\\.[a-zA-Z0-9-]+)*$",
// Regex for phone numbers.
"Phone": "^\\(*\\+*[1-9]{0,3}\\)*-*[1-9]{0,3}[-. /]*\\(*[2-9]\\d{2}\\)*[-. /]*\\d{3}[-. /]*\\d{4} *e*x*t*\\.* *\\d{0,4}$",
// Regex for slugs (e.g. hello-world).
"Slug": "^[a-z0-9]+(\\\\-[a-z0-9]+)*$",
// Regex for urls.
"Url": "^(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$"
}
},
"logging": {
/*
* Setting the flag to true, enables well formatteds json logs.

Loading…
Cancel
Save