Browse Source

Source url for GraphQL. Closes #91

pull/95/head
Sebastian Stehle 8 years ago
parent
commit
a1e0f3c979
  1. 2
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs
  2. 18
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs
  3. 4
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLContext.cs
  4. 4
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLUrlGenerator.cs
  5. 12
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/AssetGraphType.cs
  6. 7
      src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs
  7. 14
      src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs
  8. 7
      src/Squidex.Infrastructure/Assets/FolderAssetStore.cs
  9. 2
      src/Squidex.Infrastructure/Assets/IAssetStore.cs
  10. 14
      src/Squidex/Config/Domain/ReadModule.cs
  11. 14
      src/Squidex/Pipeline/GraphQLUrlGenerator.cs
  12. 6
      src/Squidex/appsettings.json
  13. 4
      tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs
  14. 7
      tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeUrlGenerator.cs
  15. 13
      tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs
  16. 10
      tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs
  17. 13
      tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs

2
src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs

@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{ {
var schemas = await schemaRepository.QueryAllAsync(app.Id); var schemas = await schemaRepository.QueryAllAsync(app.Id);
modelContext = new GraphQLModel(app, schemas.Where(x => x.IsPublished)); modelContext = new GraphQLModel(app, schemas.Where(x => x.IsPublished), urlGenerator);
Cache.Set(cacheKey, modelContext); Cache.Set(cacheKey, modelContext);
} }

18
src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs

@ -41,10 +41,14 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
private readonly IGraphType assetListType; private readonly IGraphType assetListType;
private readonly GraphQLSchema graphQLSchema; private readonly GraphQLSchema graphQLSchema;
public GraphQLModel(IAppEntity appEntity, IEnumerable<ISchemaEntity> schemas) public bool CanGenerateAssetSourceUrl { get; }
public GraphQLModel(IAppEntity appEntity, IEnumerable<ISchemaEntity> schemas, IGraphQLUrlGenerator urlGenerator)
{ {
this.appEntity = appEntity; this.appEntity = appEntity;
CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl;
partitionResolver = appEntity.PartitionResolver; partitionResolver = appEntity.PartitionResolver;
assetType = new AssetGraphType(this); assetType = new AssetGraphType(this);
@ -113,6 +117,18 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
return resolver; return resolver;
} }
public IFieldResolver ResolveAssetSourceUrl()
{
var resolver = new FuncFieldResolver<IAssetEntity, object>(c =>
{
var context = (QueryContext)c.UserContext;
return context.UrlGenerator.GenerateAssetSourceUrl(appEntity, c.Source);
});
return resolver;
}
public IFieldResolver ResolveAssetThumbnailUrl() public IFieldResolver ResolveAssetThumbnailUrl()
{ {
var resolver = new FuncFieldResolver<IAssetEntity, object>(c => var resolver = new FuncFieldResolver<IAssetEntity, object>(c =>

4
src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLContext.cs

@ -17,6 +17,8 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{ {
public interface IGraphQLContext public interface IGraphQLContext
{ {
bool CanGenerateAssetSourceUrl { get; }
IFieldPartitioning ResolvePartition(Partitioning key); IFieldPartitioning ResolvePartition(Partitioning key);
IGraphType GetAssetType(); IGraphType GetAssetType();
@ -25,6 +27,8 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
IFieldResolver ResolveAssetUrl(); IFieldResolver ResolveAssetUrl();
IFieldResolver ResolveAssetSourceUrl();
IFieldResolver ResolveAssetThumbnailUrl(); IFieldResolver ResolveAssetThumbnailUrl();
IFieldResolver ResolveContentUrl(ISchemaEntity schemaEntity); IFieldResolver ResolveContentUrl(ISchemaEntity schemaEntity);

4
src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLUrlGenerator.cs

@ -14,10 +14,14 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{ {
public interface IGraphQLUrlGenerator public interface IGraphQLUrlGenerator
{ {
bool CanGenerateAssetSourceUrl { get; }
string GenerateAssetUrl(IAppEntity appEntity, IAssetEntity assetEntity); string GenerateAssetUrl(IAppEntity appEntity, IAssetEntity assetEntity);
string GenerateAssetThumbnailUrl(IAppEntity appEntity, IAssetEntity assetEntity); string GenerateAssetThumbnailUrl(IAppEntity appEntity, IAssetEntity assetEntity);
string GenerateAssetSourceUrl(IAppEntity appEntity, IAssetEntity assetEntity);
string GenerateContentUrl(IAppEntity appEntity, ISchemaEntity schemaEntity, IContentEntity contentEntity); string GenerateContentUrl(IAppEntity appEntity, ISchemaEntity schemaEntity, IContentEntity contentEntity);
} }
} }

12
src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/AssetGraphType.cs

@ -148,6 +148,18 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types
Description = "The height of the image in pixels if the asset is an image." Description = "The height of the image in pixels if the asset is an image."
}); });
if (context.CanGenerateAssetSourceUrl)
{
AddField(new FieldType
{
Name = "sourceUrl",
Resolver = context.ResolveAssetSourceUrl(),
ResolvedType = new StringGraphType(),
Description = "The source url of the asset."
});
}
Description = "An asset"; Description = "An asset";
} }

7
src/Squidex.Infrastructure.Azure/Assets/AzureBlobAssetStore.cs

@ -50,6 +50,13 @@ namespace Squidex.Infrastructure.Assets
} }
} }
public string GenerateSourceUrl(string id, long version, string suffix)
{
var blobName = GetObjectName(id, version, suffix);
return new Uri(blobContainer.StorageUri.PrimaryUri, $"/{containerName}/{blobName}").ToString();
}
public async Task CopyTemporaryAsync(string name, string id, long version, string suffix) public async Task CopyTemporaryAsync(string name, string id, long version, string suffix)
{ {
var blobName = GetObjectName(id, version, suffix); var blobName = GetObjectName(id, version, suffix);

14
src/Squidex.Infrastructure.GoogleCloud/Assets/GoogleCloudAssetStore.cs

@ -42,6 +42,13 @@ namespace Squidex.Infrastructure.Assets
} }
} }
public string GenerateSourceUrl(string id, long version, string suffix)
{
var objectName = GetObjectName(id, version, suffix);
return $"https://storage.cloud.google.com/{bucketName}/{objectName}";
}
public Task UploadTemporaryAsync(string name, Stream stream) public Task UploadTemporaryAsync(string name, Stream stream)
{ {
return storageClient.UploadObjectAsync(bucketName, name, "application/octet-stream", stream); return storageClient.UploadObjectAsync(bucketName, name, "application/octet-stream", stream);
@ -88,9 +95,12 @@ namespace Squidex.Infrastructure.Assets
{ {
await storageClient.DeleteObjectAsync(bucketName, name); await storageClient.DeleteObjectAsync(bucketName, name);
} }
catch (GoogleApiException ex) when (ex.HttpStatusCode != HttpStatusCode.NotFound) catch (GoogleApiException ex)
{ {
throw; if (ex.HttpStatusCode != HttpStatusCode.NotFound)
{
throw;
}
} }
} }

7
src/Squidex.Infrastructure/Assets/FolderAssetStore.cs

@ -51,6 +51,13 @@ namespace Squidex.Infrastructure.Assets
} }
} }
public string GenerateSourceUrl(string id, long version, string suffix)
{
var file = GetFile(id, version, suffix);
return file.FullName;
}
public async Task UploadTemporaryAsync(string name, Stream stream) public async Task UploadTemporaryAsync(string name, Stream stream)
{ {
var file = GetFile(name); var file = GetFile(name);

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

@ -13,6 +13,8 @@ namespace Squidex.Infrastructure.Assets
{ {
public interface IAssetStore public interface IAssetStore
{ {
string GenerateSourceUrl(string id, long version, string suffix);
Task CopyTemporaryAsync(string name, string id, long version, string suffix); Task CopyTemporaryAsync(string name, string id, long version, string suffix);
Task DownloadAsync(string id, long version, string suffix, Stream stream); Task DownloadAsync(string id, long version, string suffix, Stream stream);

14
src/Squidex/Config/Domain/ReadModule.cs

@ -23,6 +23,7 @@ using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Domain.Apps.Read.Schemas.Services.Implementations; using Squidex.Domain.Apps.Read.Schemas.Services.Implementations;
using Squidex.Domain.Users; using Squidex.Domain.Users;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
using Squidex.Pipeline; using Squidex.Pipeline;
@ -46,6 +47,14 @@ namespace Squidex.Config.Domain
.AsSelf() .AsSelf()
.SingleInstance(); .SingleInstance();
builder.Register(c => new GraphQLUrlGenerator(
c.Resolve<IOptions<MyUrlsOptions>>(),
c.Resolve<IAssetStore>(),
Configuration.GetValue<bool>("assetStore:exposeSourceUrl")))
.As<IGraphQLUrlGenerator>()
.AsSelf()
.SingleInstance();
builder.RegisterType<CachingAppProvider>() builder.RegisterType<CachingAppProvider>()
.As<IAppProvider>() .As<IAppProvider>()
.AsSelf() .AsSelf()
@ -61,11 +70,6 @@ namespace Squidex.Config.Domain
.AsSelf() .AsSelf()
.SingleInstance(); .SingleInstance();
builder.RegisterType<GraphQLUrlGenerator>()
.As<IGraphQLUrlGenerator>()
.AsSelf()
.SingleInstance();
builder.RegisterType<AssetUserPictureStore>() builder.RegisterType<AssetUserPictureStore>()
.As<IUserPictureStore>() .As<IUserPictureStore>()
.AsSelf() .AsSelf()

14
src/Squidex/Pipeline/GraphQLUrlGenerator.cs

@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Read.Assets;
using Squidex.Domain.Apps.Read.Contents; using Squidex.Domain.Apps.Read.Contents;
using Squidex.Domain.Apps.Read.Contents.GraphQL; using Squidex.Domain.Apps.Read.Contents.GraphQL;
using Squidex.Domain.Apps.Read.Schemas; using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure.Assets;
// ReSharper disable ConvertIfStatementToReturnStatement // ReSharper disable ConvertIfStatementToReturnStatement
@ -20,11 +21,17 @@ namespace Squidex.Pipeline
{ {
public sealed class GraphQLUrlGenerator : IGraphQLUrlGenerator public sealed class GraphQLUrlGenerator : IGraphQLUrlGenerator
{ {
private readonly IAssetStore assetStore;
private readonly MyUrlsOptions urlsOptions; private readonly MyUrlsOptions urlsOptions;
public GraphQLUrlGenerator(IOptions<MyUrlsOptions> urlsOptions) public bool CanGenerateAssetSourceUrl { get; }
public GraphQLUrlGenerator(IOptions<MyUrlsOptions> urlsOptions, IAssetStore assetStore, bool allowAssetSourceUrl)
{ {
this.assetStore = assetStore;
this.urlsOptions = urlsOptions.Value; this.urlsOptions = urlsOptions.Value;
CanGenerateAssetSourceUrl = allowAssetSourceUrl;
} }
public string GenerateAssetThumbnailUrl(IAppEntity appEntity, IAssetEntity assetEntity) public string GenerateAssetThumbnailUrl(IAppEntity appEntity, IAssetEntity assetEntity)
@ -46,5 +53,10 @@ namespace Squidex.Pipeline
{ {
return urlsOptions.BuildUrl($"api/content/{appEntity.Name}/{schemaEntity.Name}/{contentEntity.Id}"); return urlsOptions.BuildUrl($"api/content/{appEntity.Name}/{schemaEntity.Name}/{contentEntity.Id}");
} }
public string GenerateAssetSourceUrl(IAppEntity appEntity, IAssetEntity assetEntity)
{
return assetStore.GenerateSourceUrl(assetEntity.Id.ToString(), assetEntity.FileVersion, null);
}
} }
} }

6
src/Squidex/appsettings.json

@ -61,7 +61,11 @@
* The connection string to the azure storage service. * The connection string to the azure storage service.
*/ */
"connectionString": "UseDevelopmentStorage=true" "connectionString": "UseDevelopmentStorage=true"
} },
/*
* Allow to expose the url in graph ql url.
*/
"exposeSourceUrl": false
}, },
"eventStore": { "eventStore": {

4
tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs

@ -98,6 +98,7 @@ namespace Squidex.Domain.Apps.Read.Contents
lastModifiedBy lastModifiedBy
url url
thumbnailUrl thumbnailUrl
sourceUrl
mimeType mimeType
fileName fileName
fileSize fileSize
@ -133,6 +134,7 @@ namespace Squidex.Domain.Apps.Read.Contents
lastModifiedBy = "subject:user2", lastModifiedBy = "subject:user2",
url = $"assets/{assetEntity.Id}", url = $"assets/{assetEntity.Id}",
thumbnailUrl = $"assets/{assetEntity.Id}?width=100", thumbnailUrl = $"assets/{assetEntity.Id}?width=100",
sourceUrl = $"assets/source/{assetEntity.Id}",
mimeType = "image/png", mimeType = "image/png",
fileName = "MyFile.png", fileName = "MyFile.png",
fileSize = 1024, fileSize = 1024,
@ -165,6 +167,7 @@ namespace Squidex.Domain.Apps.Read.Contents
lastModifiedBy lastModifiedBy
url url
thumbnailUrl thumbnailUrl
sourceUrl
mimeType mimeType
fileName fileName
fileSize fileSize
@ -194,6 +197,7 @@ namespace Squidex.Domain.Apps.Read.Contents
lastModifiedBy = "subject:user2", lastModifiedBy = "subject:user2",
url = $"assets/{assetEntity.Id}", url = $"assets/{assetEntity.Id}",
thumbnailUrl = $"assets/{assetEntity.Id}?width=100", thumbnailUrl = $"assets/{assetEntity.Id}?width=100",
sourceUrl = $"assets/source/{assetEntity.Id}",
mimeType = "image/png", mimeType = "image/png",
fileName = "MyFile.png", fileName = "MyFile.png",
fileSize = 1024, fileSize = 1024,

7
tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeUrlGenerator.cs

@ -15,6 +15,8 @@ namespace Squidex.Domain.Apps.Read.Contents.TestData
{ {
public sealed class FakeUrlGenerator : IGraphQLUrlGenerator public sealed class FakeUrlGenerator : IGraphQLUrlGenerator
{ {
public bool CanGenerateAssetSourceUrl { get; } = true;
public string GenerateAssetUrl(IAppEntity appEntity, IAssetEntity assetEntity) public string GenerateAssetUrl(IAppEntity appEntity, IAssetEntity assetEntity)
{ {
return $"assets/{assetEntity.Id}"; return $"assets/{assetEntity.Id}";
@ -25,6 +27,11 @@ namespace Squidex.Domain.Apps.Read.Contents.TestData
return $"assets/{assetEntity.Id}?width=100"; return $"assets/{assetEntity.Id}?width=100";
} }
public string GenerateAssetSourceUrl(IAppEntity appEntity, IAssetEntity assetEntity)
{
return $"assets/source/{assetEntity.Id}";
}
public string GenerateContentUrl(IAppEntity appEntity, ISchemaEntity schemaEntity, IContentEntity contentEntity) public string GenerateContentUrl(IAppEntity appEntity, ISchemaEntity schemaEntity, IContentEntity contentEntity)
{ {
return $"contents/{schemaEntity.Name}/{contentEntity.Id}"; return $"contents/{schemaEntity.Name}/{contentEntity.Id}";

13
tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs

@ -6,6 +6,9 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using Xunit;
namespace Squidex.Infrastructure.Assets namespace Squidex.Infrastructure.Assets
{ {
internal class AzureBlobAssetStoreTests : AssetStoreTests<AzureBlobAssetStore> internal class AzureBlobAssetStoreTests : AssetStoreTests<AzureBlobAssetStore>
@ -18,5 +21,15 @@ namespace Squidex.Infrastructure.Assets
public override void Dispose() public override void Dispose()
{ {
} }
[Fact]
public void Should_calculate_source_url()
{
Sut.Connect();
var id = Guid.NewGuid().ToString();
Assert.Equal($"http://127.0.0.1:10000/squidex-test-container/{id}_1", Sut.GenerateSourceUrl(id, 1, null));
}
} }
} }

10
tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs

@ -45,6 +45,16 @@ namespace Squidex.Infrastructure.Assets
Assert.True(Directory.Exists(testFolder)); Assert.True(Directory.Exists(testFolder));
} }
[Fact]
public void Should_calculate_source_url()
{
Sut.Connect();
var id = Guid.NewGuid().ToString();
Assert.Equal(Path.Combine(testFolder, $"{id}_1"), Sut.GenerateSourceUrl(id, 1, null));
}
private static string CreateInvalidPath() private static string CreateInvalidPath()
{ {
var windir = Environment.GetEnvironmentVariable("windir"); var windir = Environment.GetEnvironmentVariable("windir");

13
tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs

@ -6,6 +6,9 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using Xunit;
namespace Squidex.Infrastructure.Assets namespace Squidex.Infrastructure.Assets
{ {
internal class GoogleCloudAssetStoreTests : AssetStoreTests<GoogleCloudAssetStore> internal class GoogleCloudAssetStoreTests : AssetStoreTests<GoogleCloudAssetStore>
@ -18,5 +21,15 @@ namespace Squidex.Infrastructure.Assets
public override void Dispose() public override void Dispose()
{ {
} }
[Fact]
public void Should_calculate_source_url()
{
Sut.Connect();
var id = Guid.NewGuid().ToString();
Assert.Equal($"https://storage.cloud.google.com/squidex-test/{id}_1", Sut.GenerateSourceUrl(id, 1, null));
}
} }
} }

Loading…
Cancel
Save