mirror of https://github.com/Squidex/squidex.git
25 changed files with 663 additions and 75 deletions
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// BaseController.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using PinkParrot.Infrastructure.CQRS.Commands; |
||||
|
|
||||
|
namespace PinkParrot.Modules.Api |
||||
|
{ |
||||
|
public abstract class BaseController : Controller |
||||
|
{ |
||||
|
public ICommandBus CommandBus { get; } |
||||
|
|
||||
|
protected BaseController(ICommandBus commandBus) |
||||
|
{ |
||||
|
CommandBus = commandBus; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SchemaListModel.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace PinkParrot.Modules.Api.Schemas |
||||
|
{ |
||||
|
public class SchemaListModel |
||||
|
{ |
||||
|
[Required] |
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
public DateTime Created { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
public DateTime LastModified { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// MongoExtensions.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Driver; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure.MongoDb |
||||
|
{ |
||||
|
public static class MongoExtensions |
||||
|
{ |
||||
|
public static async Task<bool> InsertOneIfExistsAsync<T>(this IMongoCollection<T> collection, T document) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await collection.InsertOneAsync(document); |
||||
|
} |
||||
|
catch (MongoWriteException ex) |
||||
|
{ |
||||
|
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
throw; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IPropertyAccessor.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
namespace PinkParrot.Infrastructure.Reflection |
||||
|
{ |
||||
|
public interface IPropertyAccessor |
||||
|
{ |
||||
|
object Get(object target); |
||||
|
|
||||
|
void Set(object target, object value); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
// ==========================================================================
|
||||
|
// PropertiesTypeAccessor.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure.Reflection |
||||
|
{ |
||||
|
public sealed class PropertiesTypeAccessor |
||||
|
{ |
||||
|
private static readonly ConcurrentDictionary<Type, PropertiesTypeAccessor> AccessorCache = new ConcurrentDictionary<Type, PropertiesTypeAccessor>(); |
||||
|
private readonly Dictionary<string, IPropertyAccessor> accessors = new Dictionary<string, IPropertyAccessor>(); |
||||
|
private readonly List<PropertyInfo> properties = new List<PropertyInfo>(); |
||||
|
|
||||
|
public IEnumerable<PropertyInfo> Properties |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return properties; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private PropertiesTypeAccessor(Type type) |
||||
|
{ |
||||
|
var allProperties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); |
||||
|
|
||||
|
foreach (var property in allProperties.Where(property => property.CanRead && property.CanWrite)) |
||||
|
{ |
||||
|
accessors[property.Name] = new PropertyAccessor(type, property); |
||||
|
|
||||
|
properties.Add(property); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static PropertiesTypeAccessor Create(Type targetType) |
||||
|
{ |
||||
|
Guard.NotNull(targetType, nameof(targetType)); |
||||
|
|
||||
|
return AccessorCache.GetOrAdd(targetType, x => new PropertiesTypeAccessor(x)); |
||||
|
} |
||||
|
|
||||
|
public void SetValue(object target, string propertyName, object value) |
||||
|
{ |
||||
|
Guard.NotNull(target, "target"); |
||||
|
|
||||
|
var accessor = FindAccessor(propertyName); |
||||
|
|
||||
|
accessor.Set(target, value); |
||||
|
} |
||||
|
|
||||
|
public object GetValue(object target, string propertyName) |
||||
|
{ |
||||
|
Guard.NotNull(target, nameof(target)); |
||||
|
|
||||
|
var accessor = FindAccessor(propertyName); |
||||
|
|
||||
|
return accessor.Get(target); |
||||
|
} |
||||
|
|
||||
|
private IPropertyAccessor FindAccessor(string propertyName) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); |
||||
|
|
||||
|
IPropertyAccessor accessor; |
||||
|
|
||||
|
if (!accessors.TryGetValue(propertyName, out accessor)) |
||||
|
{ |
||||
|
throw new ArgumentException("Property does not exist.", nameof(propertyName)); |
||||
|
} |
||||
|
|
||||
|
return accessor; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
// ==========================================================================
|
||||
|
// PropertyAccessor.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure.Reflection |
||||
|
{ |
||||
|
public sealed class PropertyAccessor : IPropertyAccessor |
||||
|
{ |
||||
|
private sealed class PropertyWrapper<TObject, TValue> : IPropertyAccessor |
||||
|
{ |
||||
|
private readonly Func<TObject, TValue> getMethod; |
||||
|
private readonly Action<TObject, TValue> setMethod; |
||||
|
|
||||
|
public PropertyWrapper(PropertyInfo propertyInfo) |
||||
|
{ |
||||
|
if (propertyInfo.CanRead) |
||||
|
{ |
||||
|
getMethod = (Func<TObject, TValue>)propertyInfo.GetGetMethod(true).CreateDelegate(typeof(Func<TObject, TValue>)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
getMethod = x => { throw new NotSupportedException(); }; |
||||
|
} |
||||
|
|
||||
|
if (propertyInfo.CanWrite) |
||||
|
{ |
||||
|
setMethod = (Action<TObject, TValue>)propertyInfo.GetSetMethod(true).CreateDelegate(typeof(Action<TObject, TValue>)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
setMethod = (x, y) => { throw new NotSupportedException(); }; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public object Get(object source) |
||||
|
{ |
||||
|
return getMethod((TObject)source); |
||||
|
} |
||||
|
|
||||
|
public void Set(object source, object value) |
||||
|
{ |
||||
|
setMethod((TObject)source, (TValue)value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly IPropertyAccessor internalAccessor; |
||||
|
|
||||
|
public PropertyAccessor(Type targetType, PropertyInfo propertyInfo) |
||||
|
{ |
||||
|
Guard.NotNull(targetType, nameof(targetType)); |
||||
|
Guard.NotNull(propertyInfo, nameof(propertyInfo)); |
||||
|
|
||||
|
internalAccessor = (IPropertyAccessor)Activator.CreateInstance(typeof(PropertyWrapper<,>).MakeGenericType(propertyInfo.DeclaringType, propertyInfo.PropertyType), propertyInfo); |
||||
|
} |
||||
|
|
||||
|
public object Get(object target) |
||||
|
{ |
||||
|
Guard.NotNull(target, nameof(target)); |
||||
|
|
||||
|
return internalAccessor.Get(target); |
||||
|
} |
||||
|
|
||||
|
public void Set(object target, object value) |
||||
|
{ |
||||
|
Guard.NotNull(target, nameof(target)); |
||||
|
|
||||
|
internalAccessor.Set(target, value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,175 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SimpleMapper.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
// ReSharper disable StaticMemberInGenericType
|
||||
|
|
||||
|
namespace PinkParrot.Infrastructure.Reflection |
||||
|
{ |
||||
|
public static class SimpleMapper |
||||
|
{ |
||||
|
private sealed class ConversionPropertyMapper : PropertyMapper |
||||
|
{ |
||||
|
private readonly Type targetType; |
||||
|
|
||||
|
public ConversionPropertyMapper(IPropertyAccessor srcAccessor, IPropertyAccessor dstAccessor, Type targetType) |
||||
|
: base(srcAccessor, dstAccessor) |
||||
|
{ |
||||
|
this.targetType = targetType; |
||||
|
} |
||||
|
|
||||
|
public override void Map(object source, object destination, CultureInfo culture) |
||||
|
{ |
||||
|
var value = GetValue(source); |
||||
|
|
||||
|
if (value == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
object converted; |
||||
|
try |
||||
|
{ |
||||
|
converted = Convert.ChangeType(value, targetType, culture); |
||||
|
|
||||
|
SetValue(destination, converted); |
||||
|
} |
||||
|
catch (InvalidCastException) |
||||
|
{ |
||||
|
if (targetType == typeof(string)) |
||||
|
{ |
||||
|
converted = value.ToString(); |
||||
|
|
||||
|
SetValue(destination, converted); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class PropertyMapper |
||||
|
{ |
||||
|
private readonly IPropertyAccessor srcAccessor; |
||||
|
private readonly IPropertyAccessor dstAccessor; |
||||
|
|
||||
|
public PropertyMapper(IPropertyAccessor srcAccessor, IPropertyAccessor dstAccessor) |
||||
|
{ |
||||
|
this.srcAccessor = srcAccessor; |
||||
|
this.dstAccessor = dstAccessor; |
||||
|
} |
||||
|
|
||||
|
public virtual void Map(object source, object destination, CultureInfo culture) |
||||
|
{ |
||||
|
var value = GetValue(source); |
||||
|
|
||||
|
SetValue(destination, value); |
||||
|
} |
||||
|
|
||||
|
protected void SetValue(object destination, object value) |
||||
|
{ |
||||
|
dstAccessor.Set(destination, value); |
||||
|
} |
||||
|
|
||||
|
protected object GetValue(object source) |
||||
|
{ |
||||
|
return srcAccessor.Get(source); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class ClassMapper<TSource, TDestination> |
||||
|
where TSource : class |
||||
|
where TDestination : class |
||||
|
{ |
||||
|
private static readonly PropertyMapper[] Mappers; |
||||
|
|
||||
|
private static readonly Type[] Convertibles = |
||||
|
{ |
||||
|
typeof(bool), |
||||
|
typeof(byte), |
||||
|
typeof(char), |
||||
|
typeof(decimal), |
||||
|
typeof(float), |
||||
|
typeof(double), |
||||
|
typeof(short), |
||||
|
typeof(int), |
||||
|
typeof(long), |
||||
|
typeof(string), |
||||
|
typeof(DateTime) |
||||
|
}; |
||||
|
|
||||
|
static ClassMapper() |
||||
|
{ |
||||
|
var dstType = typeof(TDestination); |
||||
|
var srcType = typeof(TSource); |
||||
|
|
||||
|
var destinationProperties = dstType.GetProperties(); |
||||
|
|
||||
|
var newMappers = new List<PropertyMapper>(); |
||||
|
|
||||
|
foreach (var srcProperty in srcType.GetProperties().Where(x => x.CanRead)) |
||||
|
{ |
||||
|
var dstProperty = destinationProperties.FirstOrDefault(x => x.Name == srcProperty.Name); |
||||
|
|
||||
|
if (dstProperty == null || !dstProperty.CanWrite) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var srcPropertyType = srcProperty.PropertyType; |
||||
|
var dstPropertyType = dstProperty.PropertyType; |
||||
|
|
||||
|
if (srcPropertyType == dstPropertyType) |
||||
|
{ |
||||
|
newMappers.Add(new PropertyMapper(new PropertyAccessor(srcType, srcProperty), new PropertyAccessor(dstType, dstProperty))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (Convertibles.Contains(dstPropertyType)) |
||||
|
{ |
||||
|
newMappers.Add(new ConversionPropertyMapper(new PropertyAccessor(srcType, srcProperty), new PropertyAccessor(dstType, dstProperty), dstPropertyType)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Mappers = newMappers.ToArray(); |
||||
|
} |
||||
|
|
||||
|
public static TDestination Map(TSource source, TDestination destination, CultureInfo culture) |
||||
|
{ |
||||
|
foreach (var mapper in Mappers) |
||||
|
{ |
||||
|
mapper.Map(source, destination, culture); |
||||
|
} |
||||
|
|
||||
|
return destination; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static TDestination Map<TSource, TDestination>(TSource source, TDestination destination) |
||||
|
where TSource : class |
||||
|
where TDestination : class |
||||
|
{ |
||||
|
return Map(source, destination, CultureInfo.CurrentCulture); |
||||
|
} |
||||
|
|
||||
|
public static TDestination Map<TSource, TDestination>(TSource source, TDestination destination, CultureInfo culture) |
||||
|
where TSource : class |
||||
|
where TDestination : class |
||||
|
{ |
||||
|
Guard.NotNull(source, nameof(source)); |
||||
|
Guard.NotNull(culture, nameof(culture)); |
||||
|
Guard.NotNull(destination, nameof(destination)); |
||||
|
|
||||
|
return ClassMapper<TSource, TDestination>.Map(source, destination, culture); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Read.Models |
||||
|
{ |
||||
|
public interface IModelSchemaRM1 |
||||
|
{ |
||||
|
DateTime Created { get; set; } |
||||
|
string Hints { get; set; } |
||||
|
string Label { get; set; } |
||||
|
DateTime Modified { get; set; } |
||||
|
string Name { get; set; } |
||||
|
Guid SchemaId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Read.Models |
||||
|
{ |
||||
|
public interface IModelSchemaRM |
||||
|
{ |
||||
|
DateTime Created { get; set; } |
||||
|
DateTime Modified { get; set; } |
||||
|
Guid SchemaId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IEntity.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Read.Models |
||||
|
{ |
||||
|
public interface IEntity |
||||
|
{ |
||||
|
Guid Id { get; set; } |
||||
|
|
||||
|
DateTime Created { get; set; } |
||||
|
|
||||
|
DateTime LastModified { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ITenantEntity.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Read.Models |
||||
|
{ |
||||
|
public interface ITenantEntity |
||||
|
{ |
||||
|
Guid TenantId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EntityMapper.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Driver; |
||||
|
using PinkParrot.Infrastructure.CQRS; |
||||
|
using PinkParrot.Infrastructure.MongoDb; |
||||
|
using PinkParrot.Read.Models; |
||||
|
|
||||
|
namespace PinkParrot.Read.Repositories.Implementations |
||||
|
{ |
||||
|
public static class EntityMapper |
||||
|
{ |
||||
|
public static T Create<T>(EnvelopeHeaders headers) where T : IEntity, new() |
||||
|
{ |
||||
|
var timestamp = headers.Timestamp().ToDateTimeUtc(); |
||||
|
|
||||
|
var entity = new T { Id = headers.AggregateId(), Created = timestamp }; |
||||
|
|
||||
|
var tenantEntity = entity as ITenantEntity; |
||||
|
|
||||
|
if (tenantEntity != null) |
||||
|
{ |
||||
|
tenantEntity.TenantId = headers.TenantId(); |
||||
|
} |
||||
|
|
||||
|
return Update(entity, headers); |
||||
|
} |
||||
|
|
||||
|
public static T Update<T>(T entity, EnvelopeHeaders headers) where T : IEntity |
||||
|
{ |
||||
|
var timestamp = headers.Timestamp().ToDateTimeUtc(); |
||||
|
|
||||
|
entity.LastModified = timestamp; |
||||
|
|
||||
|
return entity; |
||||
|
} |
||||
|
|
||||
|
public static Task CreateAsync<T>(this IMongoCollection<T> collection, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity, new() |
||||
|
{ |
||||
|
var entity = Create<T>(headers); |
||||
|
|
||||
|
updater(entity); |
||||
|
|
||||
|
return collection.InsertOneIfExistsAsync(entity); |
||||
|
} |
||||
|
|
||||
|
public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity |
||||
|
{ |
||||
|
var entity = await collection.Find(t => t.Id == headers.AggregateId()).FirstOrDefaultAsync(); |
||||
|
|
||||
|
if (entity == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Update(entity, headers); |
||||
|
|
||||
|
updater(entity); |
||||
|
|
||||
|
await collection.ReplaceOneAsync(t => t.Id == entity.Id, entity); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue