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