Browse Source

First upload code.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
7895868149
  1. 6
      src/Squidex.Events/Assets/AssetCreated.cs
  2. 2
      src/Squidex.Events/Assets/AssetRenamed.cs
  3. 4
      src/Squidex.Events/Assets/AssetUpdated.cs
  4. 2
      src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs
  5. 23
      src/Squidex.Infrastructure/Assets/ImageInfo.cs
  6. 13
      src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs
  7. 16
      src/Squidex.Read.MongoDb/Assets/MongoAssetEntity.cs
  8. 7
      src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs
  9. 22
      src/Squidex.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs
  10. 8
      src/Squidex.Read/Assets/IAssetEntity.cs
  11. 59
      src/Squidex.Write/Assets/AssetCommandHandler.cs
  12. 23
      src/Squidex.Write/Assets/AssetDomainObject.cs
  13. 4
      src/Squidex.Write/Assets/Commands/CreateAsset.cs
  14. 6
      src/Squidex.Write/Assets/Commands/RenameAsset.cs
  15. 23
      src/Squidex.Write/Assets/Commands/UpdateAsset.cs
  16. BIN
      src/Squidex/Assets/776fe199-0beb-4d64-b10c-01bdda742123_0
  17. BIN
      src/Squidex/Assets/84ede32b-edd6-4859-8ca2-4de831a5d305_0
  18. BIN
      src/Squidex/Assets/c1a2f5ee-df8e-4930-932d-031bcbdf959d_0
  19. 6
      src/Squidex/Config/Domain/InfrastructureModule.cs
  20. 22
      src/Squidex/Config/Domain/StoreMongoDbModule.cs
  21. 4
      src/Squidex/Config/Domain/WriteModule.cs
  22. 81
      src/Squidex/Controllers/Api/Assets/AssetController.cs
  23. 107
      src/Squidex/Controllers/Api/Assets/Models/AssetDto.cs
  24. 23
      src/Squidex/Controllers/Api/Assets/Models/AssetsDto.cs
  25. 2
      src/Squidex/Controllers/Api/EntityCreatedDto.cs
  26. 2
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  27. 2
      src/Squidex/Controllers/ContentApi/Models/ContentsDto.cs
  28. 1
      src/Squidex/Startup.cs
  29. 1
      src/Squidex/app/features/assets/declarations.ts
  30. 2
      src/Squidex/app/features/assets/module.ts
  31. 7
      src/Squidex/app/features/assets/pages/asset.component.html
  32. 11
      src/Squidex/app/features/assets/pages/asset.component.scss
  33. 77
      src/Squidex/app/features/assets/pages/asset.component.ts
  34. 21
      src/Squidex/app/features/assets/pages/assets-page.component.html
  35. 22
      src/Squidex/app/features/assets/pages/assets-page.component.scss
  36. 60
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  37. 79
      src/Squidex/app/framework/angular/file-drop.directive.ts
  38. 1
      src/Squidex/app/framework/declarations.ts
  39. 3
      src/Squidex/app/framework/module.ts
  40. 117
      src/Squidex/app/shared/services/assets.service.ts
  41. 93
      tests/Squidex.Write.Tests/Assets/AssetCommandHandlerTests.cs
  42. 211
      tests/Squidex.Write.Tests/Assets/AssetDomainObjectTests.cs
  43. 10
      tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs

6
src/Squidex.Events/Assets/AssetCreated.cs

@ -13,12 +13,16 @@ namespace Squidex.Events.Assets
[TypeName("AssetCreatedEvent")]
public class AssetCreated : AssetEvent
{
public string Name { get; set; }
public string FileName { get; set; }
public string MimeType { get; set; }
public long FileSize { get; set; }
public bool IsImage { get; set; }
public int? PixelWidth { get; set; }
public int? PixelHeight { get; set; }
}
}

2
src/Squidex.Events/Assets/AssetRenamed.cs

@ -13,6 +13,6 @@ namespace Squidex.Events.Assets
[TypeName("AssetRenamedEvent")]
public class AssetRenamed : AssetEvent
{
public string Name { get; set; }
public string FileName { get; set; }
}
}

4
src/Squidex.Events/Assets/AssetUpdated.cs

@ -18,5 +18,9 @@ namespace Squidex.Events.Assets
public long FileSize { get; set; }
public bool IsImage { get; set; }
public int? PixelWidth { get; set; }
public int? PixelHeight { get; set; }
}
}

2
src/Squidex.Infrastructure/Assets/IAssetThumbnailGenerator.cs

@ -13,7 +13,7 @@ namespace Squidex.Infrastructure.Assets
{
public interface IAssetThumbnailGenerator
{
Task<bool> IsValidImageAsync(Stream input);
Task<ImageInfo> GetImageInfoAsync(Stream input);
Task<Stream> GetThumbnailOrNullAsync(Stream input, int dimension);
}

23
src/Squidex.Infrastructure/Assets/ImageInfo.cs

@ -0,0 +1,23 @@
// ==========================================================================
// ImageInfo.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Infrastructure.Assets
{
public sealed class ImageInfo
{
public int PixelWidth { get; }
public int PixelHeight { get; }
public ImageInfo(int pixelWidth, int pixelHeight)
{
PixelWidth = pixelWidth;
PixelHeight = pixelHeight;
}
}
}

13
src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs

@ -43,7 +43,7 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
});
}
public Task<bool> IsValidImageAsync(Stream input)
public Task<ImageInfo> GetImageInfoAsync(Stream input)
{
return Task.Run(() =>
{
@ -51,11 +51,18 @@ namespace Squidex.Infrastructure.Assets.ImageSharp
{
var image = new Image(input);
return image.Width > 0 && image.Height > 0;
if (image.Width > 0 && image.Height > 0)
{
return new ImageInfo(image.Width, image.Height);
}
else
{
return null;
}
}
catch
{
return false;
return null;
}
});
}

16
src/Squidex.Read.MongoDb/Assets/MongoAssetEntity.cs

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
@ -16,16 +14,28 @@ namespace Squidex.Read.MongoDb.Assets
[BsonRequired]
[BsonElement]
public string Name { get; set; }
public string FileName { get; set; }
[BsonRequired]
[BsonElement]
public long FileSize { get; set; }
[BsonRequired]
[BsonElement]
public bool IsImage { get; set; }
[BsonRequired]
[BsonElement]
public long Version { get; set; }
[BsonRequired]
[BsonElement]
public int? PixelWidth { get; set; }
[BsonRequired]
[BsonElement]
public int? PixelHeight { get; set; }
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }

7
src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs

@ -12,13 +12,14 @@ using System.Linq;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Assets;
using Squidex.Read.Assets.Repositories;
namespace Squidex.Read.MongoDb.Assets
{
public partial class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository
public partial class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository, IEventConsumer
{
public MongoAssetRepository(IMongoDatabase database)
: base(database)
@ -53,7 +54,7 @@ namespace Squidex.Read.MongoDb.Assets
return entity;
}
private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, HashSet<string> mimeTypes, string query)
private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, ICollection<string> mimeTypes, string query)
{
var filters = new List<FilterDefinition<MongoAssetEntity>>
{
@ -67,7 +68,7 @@ namespace Squidex.Read.MongoDb.Assets
if (!string.IsNullOrWhiteSpace(query))
{
filters.Add(Filter.Regex(x => x.Name, new BsonRegularExpression(query, "i")));
filters.Add(Filter.Regex(x => x.FileName, new BsonRegularExpression(query, "i")));
}
var filter = Filter.And(filters);

22
src/Squidex.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs

@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
// ==========================================================================
// MongoAssetRepository_EventHandling.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Events.Apps;
using Squidex.Events.Assets;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
@ -13,6 +17,16 @@ namespace Squidex.Read.MongoDb.Assets
{
public partial class MongoAssetRepository
{
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^asset-"; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);

8
src/Squidex.Read/Assets/IAssetEntity.cs

@ -12,8 +12,14 @@ namespace Squidex.Read.Assets
{
string MimeType { get; }
string Name { get; }
string FileName { get; }
long FileSize { get; }
bool IsImage { get; }
int? PixelWidth { get; }
int? PixelHeight { get; }
}
}

59
src/Squidex.Write/Assets/AssetCommandHandler.cs

@ -0,0 +1,59 @@
// ==========================================================================
// AssetCommandHandler.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Tasks;
using Squidex.Write.Assets.Commands;
namespace Squidex.Write.Assets
{
public class AssetCommandHandler : ICommandHandler
{
private readonly IAggregateHandler handler;
public AssetCommandHandler(IAggregateHandler handler)
{
Guard.NotNull(handler, nameof(handler));
this.handler = handler;
}
protected async Task On(CreateAsset command, CommandContext context)
{
await handler.CreateAsync<AssetDomainObject>(context, c =>
{
c.Create(command);
context.Succeed(EntityCreatedResult.Create(c.Id, c.Version));
});
}
protected async Task On(RenameAsset command, CommandContext context)
{
await handler.UpdateAsync<AssetDomainObject>(context, c => c.Rename(command));
}
protected async Task On(UpdateAsset command, CommandContext context)
{
await handler.UpdateAsync<AssetDomainObject>(context, c => c.Update(command));
}
protected Task On(DeleteAsset command, CommandContext context)
{
return handler.UpdateAsync<AssetDomainObject>(context, c => c.Delete(command));
}
public Task<bool> HandleAsync(CommandContext context)
{
return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context);
}
}
}

23
src/Squidex.Write/Assets/AssetDomainObject.cs

@ -41,12 +41,12 @@ namespace Squidex.Write.Assets
protected void On(AssetCreated @event)
{
fileName = @event.Name;
fileName = @event.FileName;
}
protected void On(AssetRenamed @event)
{
fileName = @event.Name;
fileName = @event.FileName;
}
protected void On(AssetDeleted @event)
@ -60,7 +60,7 @@ namespace Squidex.Write.Assets
VerifyNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AssetCreated { Name = command.FileName }));
RaiseEvent(SimpleMapper.Map(command, new AssetCreated()));
return this;
}
@ -76,12 +76,23 @@ namespace Squidex.Write.Assets
return this;
}
public AssetDomainObject Rename(RenameAsset command)
public AssetDomainObject Update(UpdateAsset command)
{
Guard.NotNull(command, nameof(command));
VerifyCreatedAndNotDeleted();
VerifyDifferentNames(command.Name, () => "Cannot rename asset.");
RaiseEvent(SimpleMapper.Map(command, new AssetUpdated()));
return this;
}
public AssetDomainObject Rename(RenameAsset command)
{
Guard.Valid(command, nameof(command), () => "Cannot rename asset.");
VerifyCreatedAndNotDeleted();
VerifyDifferentNames(command.FileName, () => "Cannot rename asset.");
RaiseEvent(SimpleMapper.Map(command, new AssetRenamed()));
@ -106,7 +117,7 @@ namespace Squidex.Write.Assets
private void VerifyCreatedAndNotDeleted()
{
if (isDeleted || !string.IsNullOrWhiteSpace(fileName))
if (isDeleted || string.IsNullOrWhiteSpace(fileName))
{
throw new DomainException("Asset has already been deleted or not created yet.");
}

4
src/Squidex.Write/Assets/Commands/CreateAsset.cs

@ -17,5 +17,9 @@ namespace Squidex.Write.Assets.Commands
public long FileSize { get; set; }
public bool IsImage { get; set; }
public int? PixelWidth { get; set; }
public int? PixelHeight { get; set; }
}
}

6
src/Squidex.Write/Assets/Commands/RenameAsset.cs

@ -13,13 +13,13 @@ namespace Squidex.Write.Assets.Commands
{
public sealed class RenameAsset : AssetAggregateCommand, IValidatable
{
public string Name { get; set; }
public string FileName { get; set; }
public void Validate(IList<ValidationError> errors)
{
if (!string.IsNullOrWhiteSpace(Name))
if (string.IsNullOrWhiteSpace(FileName))
{
errors.Add(new ValidationError("Name must not be null or empty.", nameof(Name)));
errors.Add(new ValidationError("File name must not be null or empty.", nameof(FileName)));
}
}
}

23
src/Squidex.Write/Assets/Commands/UpdateAsset.cs

@ -0,0 +1,23 @@
// ==========================================================================
// UpdateAssetCommand.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Write.Assets.Commands
{
public class UpdateAsset : AssetAggregateCommand
{
public string MimeType { get; set; }
public long FileSize { get; set; }
public bool IsImage { get; set; }
public int? PixelWidth { get; set; }
public int? PixelHeight { get; set; }
}
}

BIN
src/Squidex/Assets/776fe199-0beb-4d64-b10c-01bdda742123_0

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
src/Squidex/Assets/84ede32b-edd6-4859-8ca2-4de831a5d305_0

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
src/Squidex/Assets/c1a2f5ee-df8e-4930-932d-031bcbdf959d_0

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

6
src/Squidex/Config/Domain/InfrastructureModule.cs

@ -18,6 +18,8 @@ using NodaTime;
using Squidex.Core.Schemas;
using Squidex.Core.Schemas.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Assets.ImageSharp;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.Events;
@ -122,6 +124,10 @@ namespace Squidex.Config.Domain
.As<IStreamNameResolver>()
.SingleInstance();
builder.RegisterType<ImageSharpAssetThumbnailGenerator>()
.As<IAssetThumbnailGenerator>()
.SingleInstance();
builder.RegisterType<EventDataFormatter>()
.AsSelf()
.SingleInstance();

22
src/Squidex/Config/Domain/StoreMongoDbModule.cs

@ -18,9 +18,11 @@ using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Apps.Services.Implementations;
using Squidex.Read.Assets.Repositories;
using Squidex.Read.Contents.Repositories;
using Squidex.Read.History.Repositories;
using Squidex.Read.MongoDb.Apps;
using Squidex.Read.MongoDb.Assets;
using Squidex.Read.MongoDb.Contents;
using Squidex.Read.MongoDb.History;
using Squidex.Read.MongoDb.Infrastructure;
@ -123,29 +125,37 @@ namespace Squidex.Config.Domain
.AsSelf()
.SingleInstance();
builder.RegisterType<MongoHistoryEventRepository>()
builder.RegisterType<MongoAppRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration))
.As<IHistoryEventRepository>()
.As<IAppRepository>()
.As<IEventConsumer>()
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
builder.RegisterType<MongoSchemaRepository>()
builder.RegisterType<MongoAssetRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration))
.As<ISchemaRepository>()
.As<IAssetRepository>()
.As<IEventConsumer>()
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
builder.RegisterType<MongoAppRepository>()
builder.RegisterType<MongoHistoryEventRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration))
.As<IAppRepository>()
.As<IHistoryEventRepository>()
.As<IEventConsumer>()
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
builder.RegisterType<MongoSchemaRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration))
.As<ISchemaRepository>()
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
builder.Register(c =>
new CompoundEventConsumer(
c.Resolve<MongoSchemaRepository>(),

4
src/Squidex/Config/Domain/WriteModule.cs

@ -63,6 +63,10 @@ namespace Squidex.Config.Domain
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<AssetCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<ContentCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();

81
src/Squidex/Controllers/Api/Assets/AssetsController.cs → src/Squidex/Controllers/Api/Assets/AssetController.cs

@ -1,5 +1,5 @@
// ==========================================================================
// AssetsController.cs
// AssetController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@ -7,20 +7,27 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NSwag.Annotations;
using Squidex.Controllers.Api.Assets.Models;
using Squidex.Core.Identity;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Reflection;
using Squidex.Pipeline;
using Squidex.Read.Assets.Repositories;
using Squidex.Write.Assets.Commands;
#pragma warning disable 1573
namespace Squidex.Controllers.Api.Assets
{
/// <summary>
@ -30,41 +37,87 @@ namespace Squidex.Controllers.Api.Assets
[ApiExceptionFilter]
[ServiceFilter(typeof(AppFilterAttribute))]
[SwaggerTag("Assets")]
public class AssetsController : ControllerBase
public class AssetController : ControllerBase
{
private readonly IAssetStore assetStorage;
private readonly IAssetRepository assetRepository;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
private readonly AssetConfig assetsConfig;
public AssetsController(
public AssetController(
ICommandBus commandBus,
IAssetStore assetStorage,
IAssetRepository assetRepository,
IAssetThumbnailGenerator assetThumbnailGenerator,
IOptions<AssetConfig> assetsConfig)
: base(commandBus)
{
this.assetStorage = assetStorage;
this.assetThumbnailGenerator = assetThumbnailGenerator;
this.assetsConfig = assetsConfig.Value;
this.assetRepository = assetRepository;
this.assetThumbnailGenerator = assetThumbnailGenerator;
}
/// <summary>
/// Get assets.
/// </summary>
/// <returns>
/// 200 => assets returned.
/// </returns>
[HttpGet]
[Route("apps/{app}/assets/")]
[ProducesResponseType(typeof(AssetsDto), 200)]
public async Task<IActionResult> GetAssets([FromQuery] string query = null, [FromQuery] string mimeTypes = null, [FromQuery] int skip = 0, [FromQuery] int take = 10)
{
var mimeTypeList = new HashSet<string>();
if (!string.IsNullOrWhiteSpace(mimeTypes))
{
foreach (var mimeType in mimeTypes.Split(','))
{
mimeTypeList.Add(mimeType.Trim());
}
}
var taskForAssets = assetRepository.QueryAsync(AppId, mimeTypeList, query, take, skip);
var taskForCount = assetRepository.CountAsync(AppId, mimeTypeList, query);
await Task.WhenAll(taskForAssets, taskForCount);
var model = new AssetsDto
{
Total = taskForCount.Result,
Items = taskForAssets.Result.Select(x => SimpleMapper.Map(x, new AssetDto())).ToArray()
};
return Ok(model);
}
/// <summary>
/// Creates and uploads a new asset.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="file">The name of the schema.</param>
/// <returns>
/// 201 => Asset created.
/// 404 => App not found.
/// 400 => Asset exceeds the maximum size.
/// </returns>
[HttpPost]
[Route("apps/{app}/schemas/{name}/fields/")]
[ProducesResponseType(typeof(EntityCreatedDto), 201)]
[Route("apps/{app}/assets/")]
[ProducesResponseType(typeof(AssetDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ProducesResponseType(typeof(ErrorDto), 400)]
public async Task<IActionResult> PostAsset(string app, IFormFile file)
public async Task<IActionResult> PostAsset(string app, List<IFormFile> files)
{
if (files.Count != 1)
{
var error = new ValidationError($"Can only upload one file, found ${files.Count}.");
throw new ValidationException("Cannot create asset.", error);
}
var file = files[0];
if (file.Length > assetsConfig.MaxSize)
{
var error = new ValidationError($"File size cannot be longer than ${assetsConfig.MaxSize}.");
@ -78,13 +131,17 @@ namespace Squidex.Controllers.Api.Assets
fileContent.Position = 0;
var imageInfo = await assetThumbnailGenerator.GetImageInfoAsync(fileContent);
var command = new CreateAsset
{
AssetId = Guid.NewGuid(),
FileSize = file.Length,
FileName = file.Name,
FileName = file.FileName,
MimeType = file.ContentType,
IsImage = await assetThumbnailGenerator.IsValidImageAsync(fileContent)
IsImage = imageInfo != null,
PixelWidth = imageInfo?.PixelWidth,
PixelHeight = imageInfo?.PixelHeight
};
fileContent.Position = 0;
@ -93,8 +150,8 @@ namespace Squidex.Controllers.Api.Assets
var context = await CommandBus.PublishAsync(command);
var result = context.Result<EntityCreatedResult<Guid>>().IdOrValue;
var response = new EntityCreatedDto { Id = result.ToString() };
var result = context.Result<EntityCreatedResult<Guid>>();
var response = AssetDto.Create(command, result);
return StatusCode(201, response);
}

107
src/Squidex/Controllers/Api/Assets/Models/AssetDto.cs

@ -0,0 +1,107 @@
// ==========================================================================
// AssetDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using NodaTime;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Write.Assets.Commands;
namespace Squidex.Controllers.Api.Assets.Models
{
public sealed class AssetDto
{
/// <summary>
/// The id of the asset.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// The file name.
/// </summary>
[Required]
public string FileName { get; set; }
/// <summary>
/// The mime type.
/// </summary>
[Required]
public string MimeType { get; set; }
/// <summary>
/// The size of the file in bytes.
/// </summary>
public long FileSize { get; set; }
/// <summary>
/// Determines of the created file is an image.
/// </summary>
public bool IsImage { get; set; }
/// <summary>
/// The width of the image in pixels if the asset is an image.
/// </summary>
public int? PixelWidth { get; set; }
/// <summary>
/// The height of the image in pixels if the asset is an image.
/// </summary>
public int? PixelHeight { get; set; }
/// <summary>
/// The user that has created the schema.
/// </summary>
[Required]
public RefToken CreatedBy { get; set; }
/// <summary>
/// The user that has updated the asset.
/// </summary>
[Required]
public RefToken LastModifiedBy { get; set; }
/// <summary>
/// The date and time when the asset has been created.
/// </summary>
public Instant Created { get; set; }
/// <summary>
/// The date and time when the asset has been modified last.
/// </summary>
public Instant LastModified { get; set; }
/// <summary>
/// The version of the asset.
/// </summary>
public long Version { get; set; }
public static AssetDto Create(CreateAsset command, EntityCreatedResult<Guid> result)
{
var now = SystemClock.Instance.GetCurrentInstant();
var response = new AssetDto
{
Id = result.IdOrValue,
Version = result.Version,
Created = now,
CreatedBy = command.Actor,
LastModified = now,
LastModifiedBy = command.Actor,
FileName = command.FileName,
FileSize = command.FileSize,
MimeType = command.MimeType,
IsImage = command.IsImage,
PixelWidth = command.PixelWidth,
PixelHeight = command.PixelHeight
};
return response;
}
}
}

23
src/Squidex/Controllers/Api/Assets/Models/AssetsDto.cs

@ -0,0 +1,23 @@
// ==========================================================================
// AssetsDto.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Controllers.Api.Assets.Models
{
public sealed class AssetsDto
{
/// <summary>
/// The total number of assets.
/// </summary>
public long Total { get; set; }
/// <summary>
/// The assets.
/// </summary>
public AssetDto[] Items { get; set; }
}
}

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

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

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

@ -64,7 +64,7 @@ namespace Squidex.Controllers.ContentApi
await Task.WhenAll(taskForContents, taskForCount);
var model = new ContentsDto
var model = new AssetsDto
{
Total = taskForCount.Result,
Items = taskForContents.Result.Take(200).Select(x =>

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

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

1
src/Squidex/Startup.cs

@ -77,6 +77,7 @@ namespace Squidex
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterModule(new AssetStoreModule(Configuration));
builder.RegisterModule(new EventPublishersModule(Configuration));
builder.RegisterModule(new EventStoreModule(Configuration));
builder.RegisterModule(new InfrastructureModule(Configuration));

1
src/Squidex/app/features/assets/declarations.ts

@ -5,4 +5,5 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './pages/asset.component';
export * from './pages/assets-page.component';

2
src/Squidex/app/features/assets/module.ts

@ -11,6 +11,7 @@ import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule } from 'shared';
import {
AssetComponent,
AssetsPageComponent
} from './declarations';
@ -27,6 +28,7 @@ const routes: Routes = [
RouterModule.forChild(routes)
],
declarations: [
AssetComponent,
AssetsPageComponent
]
})

7
src/Squidex/app/features/assets/pages/asset.component.html

@ -0,0 +1,7 @@
<div class="card">
<div class="card-block">
</div>
<div class="card-footer">
{{fileInfo}}
</div>
</div>

11
src/Squidex/app/features/assets/pages/asset.component.scss

@ -0,0 +1,11 @@
@import '_vars';
@import '_mixins';
$card-size: 16rem;
.card {
width: $card-size;
height: $card-size;
margin-right: 1rem;
margin-bottom: 1rem;
}

77
src/Squidex/app/features/assets/pages/asset.component.ts

@ -0,0 +1,77 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, Input, OnInit } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
AssetDto,
AssetsService,
NotificationService,
UsersProviderService
} from 'shared';
@Component({
selector: 'sqx-asset',
styleUrls: ['./asset.component.scss'],
templateUrl: './asset.component.html'
})
export class AssetComponent extends AppComponentBase implements OnInit {
@Input()
public initFile: File;
@Input()
public asset: AssetDto;
public get fileInfo(): string {
let result = '';
if (this.asset != null) {
if (this.asset.pixelWidth) {
result = `${this.asset.pixelWidth}x${this.asset.pixelHeight}px, `;
}
result += fileSize(this.asset.fileSize);
}
return result;
}
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly assetsService: AssetsService
) {
super(notifications, users, apps);
}
public ngOnInit() {
const initFile = this.initFile;
if (initFile) {
this.appName()
.switchMap(app => this.assetsService.uploadFile(app, initFile))
.subscribe(result => {
if (result instanceof AssetDto) {
this.asset = result;
}
}, error => {
this.notifyError(error);
});
}
}
}
function fileSize(b: number) {
let u = 0, s = 1024;
while (b >= s || -b >= s) {
b /= s;
u++;
}
return (u ? b.toFixed(1) + ' ' : b) + ' kMGTPEZY'[u] + 'B';
}

21
src/Squidex/app/features/assets/pages/assets-page.component.html

@ -1,6 +1,6 @@
<sqx-title message="{app} | Assets" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<sqx-panel panelWidth="60rem">
<sqx-panel panelWidth="80rem">
<div class="panel-header">
<div class="panel-title-row">
<h3 class="panel-title">Assets</h3>
@ -13,7 +13,26 @@
<div class="panel-main">
<div class="panel-content panel-content-scroll">
<div class="file-drop" (sqxFileDrop)="onDrop($event)">
<h3 class="file-drop-header">Drop files here to upload</h3>
<div class="file-drop-or">or</div>
<div class="file-drop-button">
<button class="btn btn-success">Select Files</button>
</div>
<div class="file-drop-info">Drop file on existing item to replace the asset with a newer version.</div>
</div>
<div>
<span *ngFor="let file of newFiles">
<sqx-asset [initFile]="file"></sqx-asset>
</span>
<span *ngFor="let asset of assetsItems">
<sqx-asset [asset]="asset"></sqx-asset>
</span>
</div>
</div>
</div>
</sqx-panel>

22
src/Squidex/app/features/assets/pages/assets-page.component.scss

@ -1,2 +1,24 @@
@import '_vars';
@import '_mixins';
.file-drop {
& {
border: 2px dashed $color-border;
background: transparent;
padding: 1rem;
text-align: center;
margin-bottom: 1rem;
}
&-or {
font-size: .8rem;
}
&-button {
margin: .5rem 0;
}
&-info {
color: $color-subtext;
}
}

60
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -5,13 +5,18 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import {
AppComponentBase,
AppsStoreService,
AssetDto,
AssetsService,
fadeAnimation,
ImmutableArray,
NotificationService,
Pager,
UsersProviderService
} from 'shared';
@ -23,9 +28,58 @@ import {
fadeAnimation
]
})
export class AssetsPageComponent extends AppComponentBase {
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService) {
export class AssetsPageComponent extends AppComponentBase implements OnInit {
public newFiles = ImmutableArray.empty<File>();
public assetsItems = ImmutableArray.empty<AssetDto>();
public assetsPager = new Pager(0);
public assetsFilter = new FormControl();
public assertQuery = '';
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly assetsService: AssetsService
) {
super(notifications, users, apps);
}
public ngOnInit() {
this.load();
}
public search() {
this.assetsPager = new Pager(0);
this.assertQuery = this.assetsFilter.value;
this.load();
}
private load() {
this.appName()
.switchMap(app => this.assetsService.getAssets(app, this.assetsPager.pageSize, this.assetsPager.skip, this.assertQuery, null))
.subscribe(dtos => {
this.assetsItems = ImmutableArray.of(dtos.items);
this.assetsPager = this.assetsPager.setCount(dtos.total);
}, error => {
this.notifyError(error);
});
}
public goNext() {
this.assetsPager = this.assetsPager.goNext();
this.load();
}
public goPrev() {
this.assetsPager = this.assetsPager.goPrev();
this.load();
}
public onDrop(files: File[]) {
for (let file of files) {
this.newFiles = this.newFiles.pushFront(file);
}
}
}

79
src/Squidex/app/framework/angular/file-drop.directive.ts

@ -0,0 +1,79 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
@Directive({
selector: '[sqxFileDrop]'
})
export class FileDropDirective {
@Output('sqxFileDrop')
public drop = new EventEmitter<File[]>();
@HostListener('dragenter', ['$event'])
public onDragEnter(event: DragDropEvent) {
this.tryStopEvent(event);
}
@HostListener('dragover', ['$event'])
public onDragOver(event: DragDropEvent) {
this.tryStopEvent(event);
}
@HostListener('drop', ['$event'])
public onDrop(event: DragDropEvent) {
const files: File[] = [];
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < event.dataTransfer.files.length; i++) {
const file = event.dataTransfer.files[i];
files.push(file);
}
this.drop.emit(files);
this.stopEvent(event);
}
private stopEvent(event: Event) {
event.preventDefault();
event.stopPropagation();
}
private tryStopEvent(event: DragDropEvent) {
const hasFiles = this.hasFiles(event.dataTransfer.types);
if (!hasFiles) {
return;
}
this.stopEvent(event);
}
private hasFiles(types: any): boolean {
if (!types) {
return false;
}
if (isFunction(types.indexOf)) {
return types.indexOf('Files') !== -1;
} else if (isFunction(types.contains)) {
return types.contains('Files');
} else {
return false;
}
}
}
function isFunction(obj: any): boolean {
return !!(obj && obj.constructor && obj.call && obj.apply);
};
interface DragDropEvent extends MouseEvent {
readonly dataTransfer: DataTransfer;
}

1
src/Squidex/app/framework/declarations.ts

@ -13,6 +13,7 @@ export * from './angular/control-errors.component';
export * from './angular/copy.directive';
export * from './angular/date-time-editor.component';
export * from './angular/date-time.pipes';
export * from './angular/file-drop.directive';
export * from './angular/focus-on-change.directive';
export * from './angular/focus-on-init.directive';
export * from './angular/geolocation-editor.component';

3
src/Squidex/app/framework/module.ts

@ -22,6 +22,7 @@ import {
DayPipe,
DisplayNamePipe,
DurationPipe,
FileDropDirective,
FocusOnChangeDirective,
FocusOnInitDirective,
FromNowPipe,
@ -72,6 +73,7 @@ import {
DayPipe,
DisplayNamePipe,
DurationPipe,
FileDropDirective,
FocusOnChangeDirective,
FocusOnInitDirective,
FromNowPipe,
@ -106,6 +108,7 @@ import {
DayPipe,
DisplayNamePipe,
DurationPipe,
FileDropDirective,
FocusOnChangeDirective,
FocusOnInitDirective,
FromNowPipe,

117
src/Squidex/app/shared/services/assets.service.ts

@ -6,12 +6,43 @@
*/
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Headers, Http } from '@angular/http';
import { Observable } from 'rxjs';
import { ApiUrlConfig, EntityCreatedDto } from 'framework';
import {
ApiUrlConfig,
DateTime,
Version
} from 'framework';
import { AuthService } from './auth.service';
export class AssetsDto {
constructor(
public readonly total: number,
public readonly items: AssetDto[]
) {
}
}
export class AssetDto {
constructor(
public readonly id: string,
public readonly createdBy: string,
public readonly lastModifiedBy: string,
public readonly created: DateTime,
public readonly lastModified: DateTime,
public readonly fileName: string,
public readonly fileSize: number,
public readonly mimeType: string,
public readonly isImage: boolean,
public readonly pixelWidth: number | null,
public readonly pixelHeight: number | null,
public readonly version: Version
) {
}
}
@Injectable()
export class AssetsService {
constructor(
@ -21,23 +52,89 @@ export class AssetsService {
) {
}
public uploadFile(appName: string, file: File): Observable<number | EntityCreatedDto> {
return new Observable<number | EntityCreatedDto>(subscriber => {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
public getAssets(appName: string, take: number, skip: number, query: string, mimeTypes: string[]): Observable<AssetsDto> {
let fullQuery = query ? query.trim() : '';
if (mimeTypes && mimeTypes.length > 0) {
let mimeQuery = '&mimeTypes=';
for (let i = 0; i < mimeTypes.length; i++) {
mimeQuery += mimeTypes[0];
if (i > 0) {
mimeQuery += ',';
}
}
fullQuery += mimeQuery;
}
if (query && query.length > 0) {
fullQuery += `&query=${query}`;
}
if (take > 0) {
fullQuery += `&take=${take}`;
}
if (skip > 0) {
fullQuery += `&skip=${skip}`;
}
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/?${fullQuery}`);
return this.authService.authGet(url)
.map(response => response.json())
.map(response => {
const items: any[] = response.items;
return new AssetsDto(response.total, items.map(item => {
return new AssetDto(
item.id,
item.createdBy,
item.lastModifiedBy,
DateTime.parseISO_UTC(item.created),
DateTime.parseISO_UTC(item.lastModified),
item.fileName,
item.fileSize,
item.mimeType,
item.isImage,
item.pixelWidth,
item.pixelHeight,
new Version(item.version.toString()));
}));
})
.catchError('Failed to load assets. Please reload.');
}
public uploadFile(appName: string, file: File): Observable<number | AssetDto> {
return new Observable<number | AssetDto>(subscriber => {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/`);
const content = new FormData();
const headers = new Headers({
'Authorization': `${this.authService.user.user.token_type} ${this.authService.user.user.access_token}`,
'Content-Type': 'multipart/form-data'
'Authorization': `${this.authService.user.user.token_type} ${this.authService.user.user.access_token}`
});
content.append('file', file);
content.append('files', file);
this.http
.post(url, content, headers)
.post(url, content, { headers })
.map(response => response.json())
.map(response => {
return new EntityCreatedDto(response.id);
return new AssetDto(
response.id,
response.createdBy,
response.lastModifiedBy,
DateTime.parseISO_UTC(response.created),
DateTime.parseISO_UTC(response.lastModified),
response.fileName,
response.fileSize,
response.mimeType,
response.isImage,
response.pixelWidth,
response.pixelHeight,
new Version(response.version.toString()));
})
.catchError('Failed to upload asset. Please reload.')
.subscribe(value => {

93
tests/Squidex.Write.Tests/Assets/AssetCommandHandlerTests.cs

@ -0,0 +1,93 @@
// ==========================================================================
// AssetCommandHandlerTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Write.Assets.Commands;
using Squidex.Write.TestHelpers;
using Xunit;
// ReSharper disable ConvertToConstant.Local
namespace Squidex.Write.Assets
{
public class AssetCommandHandlerTests : HandlerTestBase<AssetDomainObject>
{
private readonly AssetCommandHandler sut;
private readonly AssetDomainObject asset;
private readonly Guid assetId = Guid.NewGuid();
private readonly string fileName = "my-image.png";
private readonly string mimeType = "image/png";
private readonly long fileSize = 1024;
public AssetCommandHandlerTests()
{
asset = new AssetDomainObject(assetId, 0);
sut = new AssetCommandHandler(Handler);
}
[Fact]
public async Task Create_should_create_asset()
{
var context = CreateContextForCommand(new CreateAsset { AssetId = assetId, FileName = fileName, FileSize = fileSize, MimeType = mimeType });
await TestCreate(asset, async _ =>
{
await sut.HandleAsync(context);
});
Assert.Equal(assetId, context.Result<EntityCreatedResult<Guid>>().IdOrValue);
}
[Fact]
public async Task Update_should_update_domain_object()
{
CreateAsset();
var context = CreateContextForCommand(new UpdateAsset { AssetId = assetId, FileSize = fileSize, MimeType = mimeType });
await TestUpdate(asset, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task Rename_should_update_domain_object()
{
CreateAsset();
var context = CreateContextForCommand(new RenameAsset { AssetId = assetId, FileName = "my-new-image.png" });
await TestUpdate(asset, async _ =>
{
await sut.HandleAsync(context);
});
}
[Fact]
public async Task Delete_should_update_domain_object()
{
CreateAsset();
var command = CreateContextForCommand(new DeleteAsset { AssetId = assetId });
await TestUpdate(asset, async _ =>
{
await sut.HandleAsync(command);
});
}
private void CreateAsset()
{
asset.Create(new CreateAsset { FileName = fileName, FileSize = fileSize, MimeType = mimeType });
}
}
}

211
tests/Squidex.Write.Tests/Assets/AssetDomainObjectTests.cs

@ -0,0 +1,211 @@
// ==========================================================================
// AssetDomainObjectTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using Squidex.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS;
using Squidex.Write.Assets.Commands;
using Squidex.Write.TestHelpers;
using Xunit;
// ReSharper disable ConvertToConstant.Local
namespace Squidex.Write.Assets
{
public class AssetDomainObjectTests : HandlerTestBase<AssetDomainObject>
{
private readonly AssetDomainObject sut;
private readonly string fileName = "my-image.png";
private readonly string mimeType = "image/png";
private readonly long fileSize = 1024;
public Guid AssetId { get; } = Guid.NewGuid();
public AssetDomainObjectTests()
{
sut = new AssetDomainObject(AssetId, 0);
}
[Fact]
public void Create_should_throw_if_created()
{
sut.Create(new CreateAsset { FileName = fileName, FileSize = fileSize, MimeType = mimeType });
Assert.Throws<DomainException>(() =>
{
sut.Create(CreateAssetCommand(new CreateAsset { FileName = fileName, FileSize = fileSize, MimeType = mimeType }));
});
}
[Fact]
public void Create_should_create_events()
{
sut.Create(CreateAssetCommand(new CreateAsset { FileName = fileName, FileSize = fileSize, MimeType = mimeType }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetCreated { FileName = fileName, FileSize = fileSize, MimeType = mimeType })
);
}
[Fact]
public void Update_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset { FileSize = fileSize, MimeType = mimeType }));
});
}
[Fact]
public void Update_should_throw_if_asset_is_deleted()
{
CreateAsset();
DeleteAsset();
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset()));
});
}
[Fact]
public void Update_should_create_events()
{
CreateAsset();
sut.Update(CreateAssetCommand(new UpdateAsset { FileSize = fileSize, MimeType = mimeType }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetUpdated { FileSize = fileSize, MimeType = mimeType })
);
}
[Fact]
public void Rename_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset { FileSize = fileSize, MimeType = mimeType }));
});
}
[Fact]
public void Rename_should_throw_if_asset_is_deleted()
{
CreateAsset();
DeleteAsset();
Assert.Throws<DomainException>(() =>
{
sut.Update(CreateAssetCommand(new UpdateAsset()));
});
}
[Fact]
public void Rename_should_throw_if_command_is_not_valid()
{
CreateAsset();
Assert.Throws<ValidationException>(() =>
{
sut.Rename(CreateAssetCommand(new RenameAsset()));
});
}
[Fact]
public void Rename_should_throw_if_new_name_is_equal_to_old_name()
{
CreateAsset();
Assert.Throws<ValidationException>(() =>
{
sut.Rename(CreateAssetCommand(new RenameAsset { FileName = fileName }));
});
}
[Fact]
public void Rename_should_create_events()
{
CreateAsset();
sut.Rename(CreateAssetCommand(new RenameAsset { FileName = "my-new-image.png" }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetRenamed { FileName = "my-new-image.png" })
);
}
[Fact]
public void Delete_should_throw_if_not_created()
{
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));
});
}
[Fact]
public void Delete_should_throw_if_already_deleted()
{
CreateAsset();
DeleteAsset();
Assert.Throws<DomainException>(() =>
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));
});
}
[Fact]
public void Delete_should_update_properties_create_events()
{
CreateAsset();
sut.Delete(CreateAssetCommand(new DeleteAsset()));
Assert.True(sut.IsDeleted);
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetDeleted())
);
}
private void CreateAsset()
{
sut.Create(CreateAssetCommand(new CreateAsset { FileName = fileName, FileSize = fileSize, MimeType = mimeType }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void DeleteAsset()
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));
((IAggregate)sut).ClearUncommittedEvents();
}
protected T CreateAssetEvent<T>(T @event) where T : AssetEvent
{
@event.AssetId = AssetId;
return CreateEvent(@event);
}
protected T CreateAssetCommand<T>(T command) where T : AssetAggregateCommand
{
command.AssetId = AssetId;
return CreateCommand(command);
}
}
}

10
tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs

@ -82,7 +82,7 @@ namespace Squidex.Write.Contents
}
[Fact]
public void Update_should_throw_if_schema_is_deleted()
public void Update_should_throw_if_content_is_deleted()
{
CreateContent();
DeleteContent();
@ -108,7 +108,6 @@ namespace Squidex.Write.Contents
public void Update_should_create_events()
{
CreateContent();
UpdateContent();
sut.Update(CreateContentCommand(new UpdateContent { Data = otherData }));
@ -139,7 +138,7 @@ namespace Squidex.Write.Contents
}
[Fact]
public void Patch_should_throw_if_schema_is_deleted()
public void Patch_should_throw_if_content_is_deleted()
{
CreateContent();
DeleteContent();
@ -165,7 +164,6 @@ namespace Squidex.Write.Contents
public void Patch_should_create_events()
{
CreateContent();
UpdateContent();
sut.Patch(CreateContentCommand(new PatchContent { Data = otherData }));
@ -196,7 +194,7 @@ namespace Squidex.Write.Contents
}
[Fact]
public void Publish_should_throw_if_schema_is_deleted()
public void Publish_should_throw_if_content_is_deleted()
{
CreateContent();
DeleteContent();
@ -232,7 +230,7 @@ namespace Squidex.Write.Contents
}
[Fact]
public void Unpublish_should_throw_if_schema_is_deleted()
public void Unpublish_should_throw_if_content_is_deleted()
{
CreateContent();
DeleteContent();

Loading…
Cancel
Save