mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
60 changed files with 1955 additions and 117 deletions
@ -0,0 +1,51 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppPattern.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Diagnostics.Contracts; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class AppPattern |
||||
|
{ |
||||
|
private readonly string name; |
||||
|
private readonly string pattern; |
||||
|
private readonly string message; |
||||
|
|
||||
|
public string Name |
||||
|
{ |
||||
|
get { return name; } |
||||
|
} |
||||
|
|
||||
|
public string Pattern |
||||
|
{ |
||||
|
get { return pattern; } |
||||
|
} |
||||
|
|
||||
|
public string Message |
||||
|
{ |
||||
|
get { return message; } |
||||
|
} |
||||
|
|
||||
|
public AppPattern(string name, string pattern, string message = null) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(name, nameof(name)); |
||||
|
Guard.NotNullOrEmpty(pattern, nameof(pattern)); |
||||
|
|
||||
|
this.name = name; |
||||
|
this.pattern = pattern; |
||||
|
this.message = message; |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppPattern Update(string name, string pattern, string message) |
||||
|
{ |
||||
|
return new AppPattern(name, pattern, message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppPatterns.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
using System; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Diagnostics.Contracts; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps |
||||
|
{ |
||||
|
public sealed class AppPatterns : DictionaryWrapper<Guid, AppPattern> |
||||
|
{ |
||||
|
public static readonly AppPatterns Empty = new AppPatterns(); |
||||
|
|
||||
|
private AppPatterns() |
||||
|
: base(ImmutableDictionary<Guid, AppPattern>.Empty) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public AppPatterns(ImmutableDictionary<Guid, AppPattern> inner) |
||||
|
: base(inner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppPatterns Add(Guid id, string name, string pattern, string message) |
||||
|
{ |
||||
|
var newPattern = new AppPattern(name, pattern, message); |
||||
|
|
||||
|
return new AppPatterns(Inner.Add(id, newPattern)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppPatterns Remove(Guid id) |
||||
|
{ |
||||
|
return new AppPatterns(Inner.Remove(id)); |
||||
|
} |
||||
|
|
||||
|
[Pure] |
||||
|
public AppPatterns Update(Guid id, string name, string pattern, string message) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(name, nameof(name)); |
||||
|
Guard.NotNullOrEmpty(pattern, nameof(pattern)); |
||||
|
|
||||
|
if (!TryGetValue(id, out var appPattern)) |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
return new AppPatterns(Inner.SetItem(id, appPattern.Update(name, pattern, message))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppPatternsConverter.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.Immutable; |
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Infrastructure.Json; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps.Json |
||||
|
{ |
||||
|
public sealed class AppPatternsConverter : JsonClassConverter<AppPatterns> |
||||
|
{ |
||||
|
protected override void WriteValue(JsonWriter writer, AppPatterns value, JsonSerializer serializer) |
||||
|
{ |
||||
|
var json = new Dictionary<Guid, JsonAppPattern>(value.Count); |
||||
|
|
||||
|
foreach (var client in value) |
||||
|
{ |
||||
|
json.Add(client.Key, new JsonAppPattern(client.Value)); |
||||
|
} |
||||
|
|
||||
|
serializer.Serialize(writer, json); |
||||
|
} |
||||
|
|
||||
|
protected override AppPatterns ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) |
||||
|
{ |
||||
|
var json = serializer.Deserialize<Dictionary<Guid, JsonAppPattern>>(reader); |
||||
|
|
||||
|
return new AppPatterns(json.ToImmutableDictionary(x => x.Key, x => x.Value.ToPattern())); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// ==========================================================================
|
||||
|
// JsonAppPattern.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
using System; |
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Apps.Json |
||||
|
{ |
||||
|
public class JsonAppPattern |
||||
|
{ |
||||
|
[JsonProperty] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public string Pattern { get; set; } |
||||
|
|
||||
|
[JsonProperty] |
||||
|
public string Message { get; set; } |
||||
|
|
||||
|
public JsonAppPattern() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public JsonAppPattern(AppPattern pattern) |
||||
|
{ |
||||
|
SimpleMapper.Map(pattern, this); |
||||
|
} |
||||
|
|
||||
|
public AppPattern ToPattern() |
||||
|
{ |
||||
|
return new AppPattern(Name, Pattern, Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AddPattern.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class AddPattern : AppAggregateCommand |
||||
|
{ |
||||
|
public Guid PatternId { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string Pattern { get; set; } |
||||
|
|
||||
|
public string Message { get; set; } |
||||
|
|
||||
|
public AddPattern() |
||||
|
{ |
||||
|
PatternId = Guid.NewGuid(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DeletePattern.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class DeletePattern : AppAggregateCommand |
||||
|
{ |
||||
|
public Guid PatternId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// UpdatePattern.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
||||
|
{ |
||||
|
public sealed class UpdatePattern : AppAggregateCommand |
||||
|
{ |
||||
|
public Guid PatternId { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string Pattern { get; set; } |
||||
|
|
||||
|
public string Message { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardAppPattern.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Guards |
||||
|
{ |
||||
|
public static class GuardAppPattern |
||||
|
{ |
||||
|
public static void CanAdd(AppPatterns patterns, AddPattern command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
Validate.It(() => "Cannot add pattern.", error => |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(command.Name)) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern name can not be empty.", nameof(command.Name))); |
||||
|
} |
||||
|
|
||||
|
if (patterns.Values.Any(x => x.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern name is already assigned.", nameof(command.Name))); |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(command.Pattern)) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern can not be empty.", nameof(command.Pattern))); |
||||
|
} |
||||
|
else if (!command.Pattern.IsValidRegex()) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern is not a valid regular expression.", nameof(command.Pattern))); |
||||
|
} |
||||
|
|
||||
|
if (patterns.Values.Any(x => x.Pattern == command.Pattern)) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern already exists.", nameof(command.Pattern))); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void CanDelete(AppPatterns patterns, DeletePattern command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
if (!patterns.ContainsKey(command.PatternId)) |
||||
|
{ |
||||
|
throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void CanUpdate(AppPatterns patterns, UpdatePattern command) |
||||
|
{ |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
if (!patterns.ContainsKey(command.PatternId)) |
||||
|
{ |
||||
|
throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); |
||||
|
} |
||||
|
|
||||
|
Validate.It(() => "Cannot update pattern.", error => |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(command.Name)) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern name can not be empty.", nameof(command.Name))); |
||||
|
} |
||||
|
|
||||
|
if (patterns.Any(x => x.Key != command.PatternId && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern name is already assigned.", nameof(command.Name))); |
||||
|
} |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(command.Pattern)) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern can not be empty.", nameof(command.Pattern))); |
||||
|
} |
||||
|
else if (!command.Pattern.IsValidRegex()) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern is not a valid regular expression.", nameof(command.Pattern))); |
||||
|
} |
||||
|
|
||||
|
if (patterns.Any(x => x.Key != command.PatternId && x.Value.Pattern == command.Pattern)) |
||||
|
{ |
||||
|
error(new ValidationError("Pattern already exists.", nameof(command.Pattern))); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// ==========================================================================
|
||||
|
// InitialPatterns.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps |
||||
|
{ |
||||
|
public sealed class InitialPatterns : Dictionary<Guid, AppPattern> |
||||
|
{ |
||||
|
public InitialPatterns() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public InitialPatterns(Dictionary<Guid, AppPattern> patterns) |
||||
|
: base(patterns) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppPatternAdded.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Apps |
||||
|
{ |
||||
|
[EventType(nameof(AppPatternAdded))] |
||||
|
public sealed class AppPatternAdded : AppEvent |
||||
|
{ |
||||
|
public Guid PatternId { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string Pattern { get; set; } |
||||
|
|
||||
|
public string Message { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppPatternDeleted.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Apps |
||||
|
{ |
||||
|
[EventType(nameof(AppPatternDeleted))] |
||||
|
public sealed class AppPatternDeleted : AppEvent |
||||
|
{ |
||||
|
public Guid PatternId { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppPatternUpdated.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Apps |
||||
|
{ |
||||
|
[EventType(nameof(AppPatternUpdated))] |
||||
|
public sealed class AppPatternUpdated : AppEvent |
||||
|
{ |
||||
|
public Guid PatternId { get; set; } |
||||
|
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public string Pattern { get; set; } |
||||
|
|
||||
|
public string Message { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,130 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppPatternsController.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using NSwag.Annotations; |
||||
|
using Squidex.Areas.Api.Controllers.Apps.Models; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
using Squidex.Pipeline; |
||||
|
|
||||
|
namespace Squidex.Areas.Api.Controllers.Apps |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Manages and configures app patterns.
|
||||
|
/// </summary>
|
||||
|
[ApiAuthorize] |
||||
|
[MustBeAppDeveloper] |
||||
|
[ApiExceptionFilter] |
||||
|
[AppApi] |
||||
|
[SwaggerTag(nameof(Apps))] |
||||
|
public sealed class AppPatternsController : ApiController |
||||
|
{ |
||||
|
public AppPatternsController(ICommandBus commandBus) |
||||
|
: base(commandBus) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get app patterns.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <returns>
|
||||
|
/// 200 => Patterns returned.
|
||||
|
/// 404 => App not found.
|
||||
|
/// </returns>
|
||||
|
/// <remarks>
|
||||
|
/// Gets all configured regex patterns for the app with the specified name.
|
||||
|
/// </remarks>
|
||||
|
[HttpGet] |
||||
|
[Route("apps/{app}/patterns/")] |
||||
|
[ProducesResponseType(typeof(AppPatternDto[]), 200)] |
||||
|
[ApiCosts(0)] |
||||
|
public IActionResult GetPatterns(string app) |
||||
|
{ |
||||
|
var response = |
||||
|
App.Patterns.Select(x => SimpleMapper.Map(x.Value, new AppPatternDto { PatternId = x.Key })) |
||||
|
.OrderBy(x => x.Name).ToList(); |
||||
|
|
||||
|
return Ok(response); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create a new app patterm.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="request">Pattern to be added to the app.</param>
|
||||
|
/// <returns>
|
||||
|
/// 201 => Pattern generated.
|
||||
|
/// 404 => App not found.
|
||||
|
/// </returns>
|
||||
|
[HttpPost] |
||||
|
[Route("apps/{app}/patterns/")] |
||||
|
[ProducesResponseType(typeof(AppPatternDto), 201)] |
||||
|
[ApiCosts(1)] |
||||
|
public async Task<IActionResult> PostPattern(string app, [FromBody] UpdatePatternDto request) |
||||
|
{ |
||||
|
var command = SimpleMapper.Map(request, new AddPattern()); |
||||
|
|
||||
|
await CommandBus.PublishAsync(command); |
||||
|
|
||||
|
var response = SimpleMapper.Map(request, new AppPatternDto { PatternId = command.PatternId }); |
||||
|
|
||||
|
return CreatedAtAction(nameof(GetPatterns), new { app }, request); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Update an existing app patterm.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="id">The id of the pattern to be updated.</param>
|
||||
|
/// <param name="request">Pattern to be updated for the app.</param>
|
||||
|
/// <returns>
|
||||
|
/// 204 => Pattern updated.
|
||||
|
/// 404 => App not found or pattern not found.
|
||||
|
/// </returns>
|
||||
|
[HttpPut] |
||||
|
[Route("apps/{app}/patterns/{id}/")] |
||||
|
[ProducesResponseType(typeof(AppPatternDto), 201)] |
||||
|
[ApiCosts(1)] |
||||
|
public async Task<IActionResult> UpdatePattern(string app, Guid id, [FromBody] UpdatePatternDto request) |
||||
|
{ |
||||
|
var command = SimpleMapper.Map(request, new UpdatePattern { PatternId = id }); |
||||
|
|
||||
|
await CommandBus.PublishAsync(command); |
||||
|
|
||||
|
return NoContent(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Revoke an app client
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="id">The id of the pattern to be deleted.</param>
|
||||
|
/// <returns>
|
||||
|
/// 204 => Pattern removed.
|
||||
|
/// 404 => App or pattern not found.
|
||||
|
/// </returns>
|
||||
|
/// <remarks>
|
||||
|
/// Schemas using this pattern will still function using the same Regular Expression
|
||||
|
/// </remarks>
|
||||
|
[HttpDelete] |
||||
|
[Route("apps/{app}/patterns/{id}/")] |
||||
|
[ApiCosts(1)] |
||||
|
public async Task<IActionResult> DeletePattern(string app, Guid id) |
||||
|
{ |
||||
|
await CommandBus.PublishAsync(new DeletePattern { PatternId = id }); |
||||
|
|
||||
|
return NoContent(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppPatternDto.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace Squidex.Areas.Api.Controllers.Apps.Models |
||||
|
{ |
||||
|
public sealed class AppPatternDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Identifier for Pattern
|
||||
|
/// </summary>
|
||||
|
public Guid PatternId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The name of the suggestion.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The regex pattern.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public string Pattern { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The regex message.
|
||||
|
/// </summary>
|
||||
|
public string Message { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
<div class="table-items-row" [class.table-items-footer]="!pattern"> |
||||
|
<form [formGroup]="editForm" (ngSubmit)="save()" class="row no-gutters"> |
||||
|
<div class="col col-name"> |
||||
|
<sqx-control-errors for="name" [submitted]="editFormSubmitted"></sqx-control-errors> |
||||
|
|
||||
|
<input type="text" class="form-control" id="pattern-name" maxlength="100" formControlName="name" placeholder="Name" /> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<div class="col pl-2 pr-2"> |
||||
|
<sqx-control-errors for="pattern" [submitted]="editFormSubmitted"></sqx-control-errors> |
||||
|
|
||||
|
<input type="text" class="form-control" id="pattern-pattern" maxlength="1000" formControlName="pattern" placeholder="Pattern" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col col-message"> |
||||
|
<sqx-control-errors for="message" [submitted]="editFormSubmitted"></sqx-control-errors> |
||||
|
|
||||
|
<input type="text" class="form-control" id="pattern-message" maxlength="1000" formControlName="message" placeholder="Message" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col col-auto pl-2 col-options" *ngIf="pattern"> |
||||
|
<button type="submit" class="btn btn-primary" [class.disabled]="!editForm.dirty"> |
||||
|
<i class="icon-checkmark"></i> |
||||
|
</button> |
||||
|
|
||||
|
<button type="button" class="btn btn-link btn-danger" |
||||
|
(sqxConfirmClick)="removing.emit(pattern)" |
||||
|
confirmTitle="Remove pattern" |
||||
|
confirmText="Do you really want to remove this pattern?"> |
||||
|
<i class="icon-bin2"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<div class="col col-auto pl-2 col-options" *ngIf="!pattern"> |
||||
|
<button type="submit" class="btn btn-success"> |
||||
|
<i class="icon-add"></i> |
||||
|
</button> |
||||
|
|
||||
|
<button type="reset" class="btn btn-link btn-decent" (click)="cancel()"> |
||||
|
<i class="icon-close"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
@ -0,0 +1,13 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
|
|
||||
|
.col-options { |
||||
|
min-width: 7.5rem; |
||||
|
max-width: 7.5rem; |
||||
|
} |
||||
|
|
||||
|
.col-name, |
||||
|
.col-message { |
||||
|
min-width: 10rem; |
||||
|
max-width: 10rem; |
||||
|
} |
||||
@ -0,0 +1,99 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; |
||||
|
import { FormBuilder, Validators } from '@angular/forms'; |
||||
|
|
||||
|
import { |
||||
|
AppPatternDto, |
||||
|
fadeAnimation, |
||||
|
ValidatorsEx, |
||||
|
UpdatePatternDto |
||||
|
} from 'shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-pattern', |
||||
|
styleUrls: ['./pattern.component.scss'], |
||||
|
templateUrl: './pattern.component.html', |
||||
|
animations: [ |
||||
|
fadeAnimation |
||||
|
] |
||||
|
}) |
||||
|
export class PatternComponent implements OnInit { |
||||
|
@Input() |
||||
|
public isNew = false; |
||||
|
|
||||
|
@Input() |
||||
|
public pattern: AppPatternDto; |
||||
|
|
||||
|
@Output() |
||||
|
public removing = new EventEmitter<any>(); |
||||
|
|
||||
|
@Output() |
||||
|
public updating = new EventEmitter<UpdatePatternDto>(); |
||||
|
|
||||
|
public editFormSubmitted = false; |
||||
|
public editForm = |
||||
|
this.formBuilder.group({ |
||||
|
name: [ |
||||
|
'', |
||||
|
[ |
||||
|
Validators.required, |
||||
|
Validators.maxLength(100), |
||||
|
ValidatorsEx.pattern('[A-z0-9]+[A-z0-9\- ]*[A-z0-9]', 'Name can only contain letters, numbers, dashes and spaces.') |
||||
|
] |
||||
|
], |
||||
|
pattern: [ |
||||
|
'', |
||||
|
[ |
||||
|
Validators.required |
||||
|
] |
||||
|
], |
||||
|
message: [ |
||||
|
'', |
||||
|
[ |
||||
|
Validators.maxLength(1000) |
||||
|
] |
||||
|
] |
||||
|
}); |
||||
|
|
||||
|
constructor( |
||||
|
private readonly formBuilder: FormBuilder |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public ngOnInit() { |
||||
|
const pattern = this.pattern; |
||||
|
|
||||
|
if (pattern) { |
||||
|
this.editForm.setValue({ name: pattern.name, pattern: pattern.pattern, message: pattern.message || '' }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public cancel() { |
||||
|
this.editFormSubmitted = false; |
||||
|
this.editForm.reset(); |
||||
|
} |
||||
|
|
||||
|
public save() { |
||||
|
this.editFormSubmitted = true; |
||||
|
|
||||
|
if (this.editForm.valid) { |
||||
|
const requestDto = new UpdatePatternDto( |
||||
|
this.editForm.controls['name'].value, |
||||
|
this.editForm.controls['pattern'].value, |
||||
|
this.editForm.controls['message'].value); |
||||
|
|
||||
|
this.updating.emit(requestDto); |
||||
|
|
||||
|
if (!this.pattern) { |
||||
|
this.cancel(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,41 @@ |
|||||
|
<sqx-title message="{app} | Patterns | Settings" parameter1="app" [value1]="ctx.appName"></sqx-title> |
||||
|
|
||||
|
<sqx-panel desiredWidth="60rem"> |
||||
|
<div class="panel-header"> |
||||
|
<div class="panel-title-row"> |
||||
|
<h3 class="panel-title">Patterns</h3> |
||||
|
</div> |
||||
|
|
||||
|
<a class="panel-close" sqxParentLink> |
||||
|
<i class="icon-close"></i> |
||||
|
</a> |
||||
|
</div> |
||||
|
|
||||
|
<div class="panel-main"> |
||||
|
<div class="panel-content panel-content-scroll"> |
||||
|
<div *ngIf="appPatterns"> |
||||
|
<div *ngFor="let pattern of appPatterns.patterns"> |
||||
|
<sqx-pattern [pattern]="pattern" |
||||
|
(removing)="removePattern(pattern)" |
||||
|
(updating)="updatePattern(pattern, $event)"> |
||||
|
</sqx-pattern> |
||||
|
</div> |
||||
|
<sqx-pattern [isNew]="true" |
||||
|
(updating)="addPattern($event)"> |
||||
|
</sqx-pattern> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div class="panel-sidebar"> |
||||
|
<a class="panel-link" routerLink="history" routerLinkActive="active"> |
||||
|
<i class="icon-time"></i> |
||||
|
</a> |
||||
|
<a class="panel-link" routerLink="help" routerLinkActive="active"> |
||||
|
<i class="icon-help"></i> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</sqx-panel> |
||||
|
|
||||
|
<router-outlet></router-outlet> |
||||
@ -0,0 +1,2 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
@ -0,0 +1,87 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { Component, OnInit } from '@angular/core'; |
||||
|
|
||||
|
import { |
||||
|
AppContext, |
||||
|
AppPatternDto, |
||||
|
AppPatternsDto, |
||||
|
AppPatternsService, |
||||
|
HistoryChannelUpdated, |
||||
|
UpdatePatternDto |
||||
|
} from 'shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-patterns-page', |
||||
|
styleUrls: ['./patterns-page.component.scss'], |
||||
|
templateUrl: './patterns-page.component.html', |
||||
|
providers: [ |
||||
|
AppContext |
||||
|
] |
||||
|
}) |
||||
|
export class PatternsPageComponent implements OnInit { |
||||
|
public appPatterns: AppPatternsDto; |
||||
|
|
||||
|
constructor(public readonly ctx: AppContext, |
||||
|
private readonly appPatternsService: AppPatternsService |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public ngOnInit() { |
||||
|
this.load(); |
||||
|
} |
||||
|
|
||||
|
public load() { |
||||
|
this.appPatternsService.getPatterns(this.ctx.appName).retry(2) |
||||
|
.subscribe(dtos => { |
||||
|
this.updatePatterns(dtos); |
||||
|
}, error => { |
||||
|
this.ctx.notifyError(error); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public addPattern(pattern: AppPatternDto) { |
||||
|
const requestDto = new UpdatePatternDto(pattern.name, pattern.pattern, pattern.message); |
||||
|
|
||||
|
this.appPatternsService.postPattern(this.ctx.appName, requestDto, this.appPatterns.version) |
||||
|
.subscribe(dto => { |
||||
|
this.updatePatterns(this.appPatterns.addPattern(dto.payload, dto.version)); |
||||
|
}, error => { |
||||
|
this.ctx.notifyError(error); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public updatePattern(pattern: AppPatternDto, update: UpdatePatternDto) { |
||||
|
this.appPatternsService.putPattern(this.ctx.appName, pattern.patternId, update, this.appPatterns.version) |
||||
|
.subscribe(dto => { |
||||
|
this.updatePatterns(this.appPatterns.updatePattern(pattern.update(update), dto.version)); |
||||
|
}, error => { |
||||
|
this.ctx.notifyError(error); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public removePattern(pattern: AppPatternDto) { |
||||
|
this.appPatternsService.deletePattern(this.ctx.appName, pattern.patternId, this.appPatterns.version) |
||||
|
.subscribe(dto => { |
||||
|
this.updatePatterns(this.appPatterns.deletePattern(pattern, dto.version)); |
||||
|
}, error => { |
||||
|
this.ctx.notifyError(error); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private updatePatterns(patterns: AppPatternsDto) { |
||||
|
this.appPatterns = |
||||
|
new AppPatternsDto( |
||||
|
patterns.patterns.sort((a, b) => { |
||||
|
return a.name.localeCompare(b.name); |
||||
|
}), |
||||
|
patterns.version); |
||||
|
|
||||
|
this.ctx.bus.emit(new HistoryChannelUpdated()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,174 @@ |
|||||
|
/* |
||||
|
* 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, |
||||
|
AppPatternDto, |
||||
|
AppPatternsDto, |
||||
|
AppPatternsService, |
||||
|
UpdatePatternDto, |
||||
|
Version |
||||
|
} from './../'; |
||||
|
|
||||
|
describe('ApppatternsDto', () => { |
||||
|
const pattern1 = new AppPatternDto('1', 'Any', '.*', 'Message1'); |
||||
|
const pattern2 = new AppPatternDto('2', 'Number', '[0-9]', 'Message2'); |
||||
|
const pattern2_new = new AppPatternDto('2', 'Numbers', '[0-9]*', 'Message2_1'); |
||||
|
const version = new Version('1'); |
||||
|
const newVersion = new Version('2'); |
||||
|
|
||||
|
it('should update patterns when adding pattern', () => { |
||||
|
const patterns_1 = new AppPatternsDto([pattern1], version); |
||||
|
const patterns_2 = patterns_1.addPattern(pattern2, newVersion); |
||||
|
|
||||
|
expect(patterns_2.patterns).toEqual([pattern1, pattern2]); |
||||
|
expect(patterns_2.version).toEqual(newVersion); |
||||
|
}); |
||||
|
|
||||
|
it('should update patterns when removing pattern', () => { |
||||
|
const patterns_1 = new AppPatternsDto([pattern1, pattern2], version); |
||||
|
const patterns_2 = patterns_1.deletePattern(pattern1, newVersion); |
||||
|
|
||||
|
expect(patterns_2.patterns).toEqual([pattern2]); |
||||
|
expect(patterns_2.version).toEqual(newVersion); |
||||
|
}); |
||||
|
|
||||
|
it('should update patterns when updating pattern', () => { |
||||
|
const patterns_1 = new AppPatternsDto([pattern1, pattern2], version); |
||||
|
const patterns_2 = patterns_1.updatePattern(pattern2_new, newVersion); |
||||
|
|
||||
|
expect(patterns_2.patterns).toEqual([pattern1, pattern2_new]); |
||||
|
expect(patterns_2.version).toEqual(newVersion); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('AppPatternDto', () => { |
||||
|
it('should update properties when updating', () => { |
||||
|
const pattern_1 = new AppPatternDto('1', 'Number', '[0-9]', 'Message1'); |
||||
|
const pattern_2 = pattern_1.update(new UpdatePatternDto('Numbers', '[0-9]*', 'Message2')); |
||||
|
|
||||
|
expect(pattern_2.name).toBe('Numbers'); |
||||
|
expect(pattern_2.pattern).toBe('[0-9]*'); |
||||
|
expect(pattern_2.message).toBe('Message2'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('AppPatternsService', () => { |
||||
|
const version = new Version('1'); |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
TestBed.configureTestingModule({ |
||||
|
imports: [ |
||||
|
HttpClientTestingModule |
||||
|
], |
||||
|
providers: [ |
||||
|
AppPatternsService, |
||||
|
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } |
||||
|
] |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => { |
||||
|
httpMock.verify(); |
||||
|
})); |
||||
|
|
||||
|
it('should make get request to get patterns', |
||||
|
inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
let patterns: AppPatternsDto | null = null; |
||||
|
|
||||
|
patternService.getPatterns('my-app').subscribe(result => { |
||||
|
patterns = result; |
||||
|
}); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('GET'); |
||||
|
expect(req.request.headers.get('If-Match')).toBeNull(); |
||||
|
|
||||
|
req.flush([ |
||||
|
{ |
||||
|
patternId: '1', |
||||
|
pattern: '[0-9]', |
||||
|
name: 'Number', |
||||
|
message: 'Message1' |
||||
|
}, { |
||||
|
patternId: '2', |
||||
|
pattern: '[0-9]*', |
||||
|
name: 'Numbers', |
||||
|
message: 'Message2' |
||||
|
} |
||||
|
], { |
||||
|
headers: { |
||||
|
etag: '2' |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
expect(patterns).toEqual( |
||||
|
new AppPatternsDto([ |
||||
|
new AppPatternDto('1', 'Number', '[0-9]', 'Message1'), |
||||
|
new AppPatternDto('2', 'Numbers', '[0-9]*', 'Message2') |
||||
|
], new Version('2'))); |
||||
|
})); |
||||
|
|
||||
|
it('should make post request to add pattern', |
||||
|
inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
const dto = new UpdatePatternDto('Number', '[0-9]', 'Message1'); |
||||
|
|
||||
|
let pattern: AppPatternDto | null = null; |
||||
|
|
||||
|
patternService.postPattern('my-app', dto, version).subscribe(result => { |
||||
|
pattern = result.payload; |
||||
|
}); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('POST'); |
||||
|
expect(req.request.headers.get('If-Match')).toEqual(version.value); |
||||
|
|
||||
|
req.flush({ |
||||
|
patternId: '1', |
||||
|
pattern: '[0-9]', |
||||
|
name: 'Number', |
||||
|
message: 'Message1' |
||||
|
}); |
||||
|
|
||||
|
expect(pattern).toEqual(new AppPatternDto('1', 'Number', '[0-9]', 'Message1')); |
||||
|
})); |
||||
|
|
||||
|
it('should make put request to update pattern', |
||||
|
inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
const dto = new UpdatePatternDto('Number', '[0-9]', 'Message1'); |
||||
|
|
||||
|
patternService.putPattern('my-app', '1', dto, version).subscribe(); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns/1'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('PUT'); |
||||
|
expect(req.request.headers.get('If-Match')).toEqual(version.value); |
||||
|
|
||||
|
req.flush({}); |
||||
|
})); |
||||
|
|
||||
|
it('should make delete request to remove pattern', |
||||
|
inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => { |
||||
|
|
||||
|
patternService.deletePattern('my-app', '1', version).subscribe(); |
||||
|
|
||||
|
const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns/1'); |
||||
|
|
||||
|
expect(req.request.method).toEqual('DELETE'); |
||||
|
expect(req.request.headers.get('If-Match')).toEqual(version.value); |
||||
|
|
||||
|
req.flush({}); |
||||
|
})); |
||||
|
}); |
||||
@ -0,0 +1,130 @@ |
|||||
|
/* |
||||
|
* 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, |
||||
|
HTTP, |
||||
|
Version, |
||||
|
Versioned |
||||
|
} from 'framework'; |
||||
|
|
||||
|
export class AppPatternsDto { |
||||
|
constructor( |
||||
|
public readonly patterns: AppPatternDto[], |
||||
|
public readonly version: Version |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public addPattern(pattern: AppPatternDto, version: Version) { |
||||
|
return new AppPatternsDto([...this.patterns, pattern], version); |
||||
|
} |
||||
|
|
||||
|
public updatePattern(pattern: AppPatternDto, version: Version) { |
||||
|
return new AppPatternsDto(this.patterns.map(p => p.patternId === pattern.patternId ? pattern : p), version); |
||||
|
} |
||||
|
|
||||
|
public deletePattern(pattern: AppPatternDto, version: Version) { |
||||
|
return new AppPatternsDto(this.patterns.filter(c => c.patternId !== pattern.patternId), version); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class AppPatternDto { |
||||
|
constructor( |
||||
|
public readonly patternId: string, |
||||
|
public readonly name: string, |
||||
|
public readonly pattern: string, |
||||
|
public readonly message: string |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public update(update: UpdatePatternDto) { |
||||
|
return new AppPatternDto( |
||||
|
this.patternId, |
||||
|
update.name, |
||||
|
update.pattern, |
||||
|
update.message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class UpdatePatternDto { |
||||
|
constructor( |
||||
|
public readonly name: string, |
||||
|
public readonly pattern: string, |
||||
|
public readonly message: string |
||||
|
) { |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Injectable() |
||||
|
export class AppPatternsService { |
||||
|
constructor( |
||||
|
private readonly http: HttpClient, |
||||
|
private readonly apiUrl: ApiUrlConfig |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public getPatterns(appName: string): Observable<AppPatternsDto> { |
||||
|
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`); |
||||
|
|
||||
|
return HTTP.getVersioned<any>(this.http, url) |
||||
|
.map(response => { |
||||
|
const body = response.payload.body; |
||||
|
|
||||
|
const items: any[] = body; |
||||
|
|
||||
|
return new AppPatternsDto( |
||||
|
items.map(item => { |
||||
|
return new AppPatternDto( |
||||
|
item.patternId, |
||||
|
item.name, |
||||
|
item.pattern, |
||||
|
item.message); |
||||
|
}), |
||||
|
response.version); |
||||
|
}) |
||||
|
.pretifyError('Failed to add pattern. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public postPattern(appName: string, dto: UpdatePatternDto, version: Version): Observable<Versioned<AppPatternDto>> { |
||||
|
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`); |
||||
|
|
||||
|
return HTTP.postVersioned<any>(this.http, url, dto, version) |
||||
|
.map(response => { |
||||
|
const body = response.payload.body; |
||||
|
|
||||
|
const pattern = new AppPatternDto( |
||||
|
body.patternId, |
||||
|
body.name, |
||||
|
body.pattern, |
||||
|
body.message); |
||||
|
|
||||
|
return new Versioned(response.version, pattern); |
||||
|
}) |
||||
|
.pretifyError('Failed to add pattern. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public putPattern(appName: string, id: string, dto: UpdatePatternDto, version: Version): Observable<Versioned<any>> { |
||||
|
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns/${id}`); |
||||
|
|
||||
|
return HTTP.putVersioned(this.http, url, dto, version) |
||||
|
.pretifyError('Failed to update pattern. Please reload.'); |
||||
|
} |
||||
|
|
||||
|
public deletePattern(appName: string, id: string, version: Version): Observable<Versioned<any>> { |
||||
|
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns/${id}`); |
||||
|
|
||||
|
return HTTP.deleteVersioned(this.http, url, version) |
||||
|
.pretifyError('Failed to remove pattern. Please reload.'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppClientJsonTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using FluentAssertions; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Model.Apps |
||||
|
{ |
||||
|
public class AppPatternJsonTests |
||||
|
{ |
||||
|
private readonly JsonSerializer serializer = TestData.DefaultSerializer(); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_serialize_and_deserialize() |
||||
|
{ |
||||
|
var patterns = AppPatterns.Empty; |
||||
|
|
||||
|
var guid1 = Guid.NewGuid(); |
||||
|
var guid2 = Guid.NewGuid(); |
||||
|
var guid3 = Guid.NewGuid(); |
||||
|
|
||||
|
patterns = patterns.Add(guid1, "Name1", "Pattern1", "Default"); |
||||
|
patterns = patterns.Add(guid2, "Name2", "Pattern2", "Default"); |
||||
|
patterns = patterns.Add(guid3, "Name3", "Pattern3", "Default"); |
||||
|
|
||||
|
patterns = patterns.Update(guid2, "Name2 Update", "Pattern2 Update", "Default2"); |
||||
|
|
||||
|
patterns = patterns.Remove(guid1); |
||||
|
|
||||
|
var appPatterns = JToken.FromObject(patterns, serializer).ToObject<AppPatterns>(serializer); |
||||
|
|
||||
|
appPatterns.ShouldBeEquivalentTo(patterns); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AppClientsTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using FluentAssertions; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Xunit; |
||||
|
|
||||
|
#pragma warning disable SA1310 // Field names must not contain underscore
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Model.Apps |
||||
|
{ |
||||
|
public class AppPatternsTests |
||||
|
{ |
||||
|
private readonly AppPatterns patterns_1; |
||||
|
private readonly Guid firstId = Guid.NewGuid(); |
||||
|
private readonly Guid id = Guid.NewGuid(); |
||||
|
|
||||
|
public AppPatternsTests() |
||||
|
{ |
||||
|
patterns_1 = AppPatterns.Empty.Add(firstId, "Default", "Default Pattern", "Message"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_add_pattern() |
||||
|
{ |
||||
|
var patterns_2 = patterns_1.Add(id, "NewPattern", "New Pattern", "Message"); |
||||
|
|
||||
|
patterns_2[id].ShouldBeEquivalentTo(new AppPattern("NewPattern", "New Pattern", "Message")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_throw_exception_if_add_pattern_with_same_id() |
||||
|
{ |
||||
|
var patterns_2 = patterns_1.Add(id, "NewPattern", "New Pattern", "Message"); |
||||
|
|
||||
|
Assert.Throws<ArgumentException>(() => patterns_2.Add(id, "NewPattern", "New Pattern", "Message")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_update_pattern() |
||||
|
{ |
||||
|
var patterns_2 = patterns_1.Update(firstId, "UpdatePattern", "Update Pattern", "Message"); |
||||
|
|
||||
|
patterns_2[firstId].ShouldBeEquivalentTo(new AppPattern("UpdatePattern", "Update Pattern", "Message")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_same_patterns_if_pattern_not_found() |
||||
|
{ |
||||
|
var patterns_2 = patterns_1.Update(id, "NewPattern", "NewPattern", "Message"); |
||||
|
|
||||
|
Assert.Same(patterns_1, patterns_2); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_remove_pattern() |
||||
|
{ |
||||
|
var patterns_2 = patterns_1.Remove(firstId); |
||||
|
|
||||
|
Assert.Empty(patterns_2); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_do_nothing_if_remove_pattern_not_found() |
||||
|
{ |
||||
|
var patterns_2 = patterns_1.Remove(id); |
||||
|
|
||||
|
Assert.NotSame(patterns_1, patterns_2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,169 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GuardAppPatternsTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Guards; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Xunit; |
||||
|
|
||||
|
#pragma warning disable SA1310 // Field names must not contain underscore
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Write.Apps.Guards |
||||
|
{ |
||||
|
public class GuardAppPatternsTests |
||||
|
{ |
||||
|
private readonly Guid patternId = Guid.NewGuid(); |
||||
|
private readonly AppPatterns patterns_0 = AppPatterns.Empty; |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanAdd_should_throw_exception_if_name_empty() |
||||
|
{ |
||||
|
var command = new AddPattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanAdd(patterns_0, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanAdd_should_throw_exception_if_id_empty_guid() |
||||
|
{ |
||||
|
var command = new AddPattern { Name = string.Empty, Pattern = ".*" }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanAdd(patterns_0, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanAdd_should_throw_exception_if_pattern_empty() |
||||
|
{ |
||||
|
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanAdd(patterns_0, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanAdd_should_throw_exception_if_pattern_not_valid() |
||||
|
{ |
||||
|
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanAdd(patterns_0, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanAdd_should_throw_exception_if_name_exists() |
||||
|
{ |
||||
|
var patterns_1 = patterns_0.Add(Guid.NewGuid(), "any", "[a-z]", "Message"); |
||||
|
|
||||
|
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanAdd(patterns_1, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanAdd_should_not_throw_exception_if_success() |
||||
|
{ |
||||
|
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; |
||||
|
|
||||
|
GuardAppPattern.CanAdd(patterns_0, command); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanDelete_should_throw_exception_if_pattern_not_found() |
||||
|
{ |
||||
|
var command = new DeletePattern { PatternId = patternId }; |
||||
|
|
||||
|
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppPattern.CanDelete(patterns_0, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanDelete_should_not_throw_exception_if_success() |
||||
|
{ |
||||
|
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); |
||||
|
|
||||
|
var command = new DeletePattern { PatternId = patternId }; |
||||
|
|
||||
|
GuardAppPattern.CanDelete(patterns_1, command); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanUpdate_should_throw_exception_if_name_empty() |
||||
|
{ |
||||
|
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); |
||||
|
|
||||
|
var command = new UpdatePattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanUpdate(patterns_1, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanUpdate_should_throw_exception_if_pattern_empty() |
||||
|
{ |
||||
|
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); |
||||
|
|
||||
|
var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanUpdate(patterns_1, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanUpdate_should_throw_exception_if_pattern_not_valid() |
||||
|
{ |
||||
|
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); |
||||
|
|
||||
|
var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanUpdate(patterns_1, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanUpdate_should_throw_exception_if_name_exists() |
||||
|
{ |
||||
|
var id1 = Guid.NewGuid(); |
||||
|
var id2 = Guid.NewGuid(); |
||||
|
|
||||
|
var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message"); |
||||
|
var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message"); |
||||
|
|
||||
|
var command = new UpdatePattern { PatternId = id2, Name = "Pattern1", Pattern = "[0-4]" }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanUpdate(patterns_2, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanUpdate_should_throw_exception_if_pattern_exists() |
||||
|
{ |
||||
|
var id1 = Guid.NewGuid(); |
||||
|
var id2 = Guid.NewGuid(); |
||||
|
|
||||
|
var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message"); |
||||
|
var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message"); |
||||
|
|
||||
|
var command = new UpdatePattern { PatternId = id2, Name = "Pattern2", Pattern = "[0-5]" }; |
||||
|
|
||||
|
Assert.Throws<ValidationException>(() => GuardAppPattern.CanUpdate(patterns_2, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanUpdate_should_throw_exception_if_pattern_does_not_exists() |
||||
|
{ |
||||
|
var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; |
||||
|
|
||||
|
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppPattern.CanUpdate(patterns_0, command)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void CanUpdate_should_not_throw_exception_if_pattern_exist_with_valid_command() |
||||
|
{ |
||||
|
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); |
||||
|
|
||||
|
var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; |
||||
|
|
||||
|
GuardAppPattern.CanUpdate(patterns_1, command); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Migration02_AddPatterns.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Migrations; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
|
||||
|
namespace Migrate_01 |
||||
|
{ |
||||
|
public sealed class Migration02_AddPatterns : IMigration |
||||
|
{ |
||||
|
private readonly InitialPatterns initialPatterns; |
||||
|
private readonly IStateFactory stateFactory; |
||||
|
private readonly IAppRepository appRepository; |
||||
|
|
||||
|
public int FromVersion { get; } = 1; |
||||
|
|
||||
|
public int ToVersion { get; } = 2; |
||||
|
|
||||
|
public Migration02_AddPatterns( |
||||
|
InitialPatterns initialPatterns, |
||||
|
IStateFactory stateFactory, |
||||
|
IAppRepository appRepository) |
||||
|
{ |
||||
|
this.initialPatterns = initialPatterns; |
||||
|
this.appRepository = appRepository; |
||||
|
this.stateFactory = stateFactory; |
||||
|
} |
||||
|
|
||||
|
public async Task UpdateAsync() |
||||
|
{ |
||||
|
var ids = await appRepository.QueryAppIdsAsync(); |
||||
|
|
||||
|
foreach (var id in ids) |
||||
|
{ |
||||
|
var app = await stateFactory.CreateAsync<AppDomainObject>(id); |
||||
|
|
||||
|
if (app.State.Patterns.Count == 0) |
||||
|
{ |
||||
|
foreach (var pattern in initialPatterns.Values) |
||||
|
{ |
||||
|
var command = |
||||
|
new AddPattern |
||||
|
{ |
||||
|
Actor = app.State.CreatedBy, |
||||
|
AppId = new NamedId<Guid>(app.State.Id, app.State.Name), |
||||
|
Name = pattern.Name, |
||||
|
PatternId = Guid.NewGuid(), |
||||
|
Pattern = pattern.Pattern, |
||||
|
Message = pattern.Message |
||||
|
}; |
||||
|
|
||||
|
app.AddPattern(command); |
||||
|
} |
||||
|
|
||||
|
await app.WriteStateAsync(app.Version + initialPatterns.Count); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue