// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService { private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); private readonly IContentQueryService contentQuery; private readonly IGraphQLUrlGenerator urlGenerator; private readonly IAssetQueryService assetQuery; private readonly IAppProvider appProvider; public CachingGraphQLService( IMemoryCache cache, IAppProvider appProvider, IAssetQueryService assetQuery, IContentQueryService contentQuery, IGraphQLUrlGenerator urlGenerator) : base(cache) { Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(assetQuery, nameof(assetQuery)); Guard.NotNull(contentQuery, nameof(contentQuery)); Guard.NotNull(urlGenerator, nameof(urlGenerator)); this.appProvider = appProvider; this.assetQuery = assetQuery; this.contentQuery = contentQuery; this.urlGenerator = urlGenerator; } public async Task<(bool HasError, object[] Response)> QueryAsync(QueryContext context, GraphQLQuery[] queries) { Guard.NotNull(context, nameof(context)); Guard.NotNull(queries, nameof(queries)); var model = await GetModelAsync(context.App); var ctx = new GraphQLExecutionContext(context, assetQuery, contentQuery, urlGenerator); var result = await Task.WhenAll(queries.Select(q => QueryInternalAsync(model, ctx, q))); return (result.Any(x => x.HasError), result.Select(x => x.Response).ToArray()); } public async Task<(bool HasError, object Response)> QueryAsync(QueryContext context, GraphQLQuery query) { Guard.NotNull(context, nameof(context)); Guard.NotNull(query, nameof(query)); var model = await GetModelAsync(context.App); var ctx = new GraphQLExecutionContext(context, assetQuery, contentQuery, urlGenerator); var result = await QueryInternalAsync(model, ctx, query); return result; } private static async Task<(bool HasError, object Response)> QueryInternalAsync(GraphQLModel model, GraphQLExecutionContext ctx, GraphQLQuery query) { if (string.IsNullOrWhiteSpace(query.Query)) { return (false, new { Data = new object() }); } var result = await model.ExecuteAsync(ctx, query); if (result.Errors?.Any() == true) { return (false, new { result.Data, result.Errors }); } else { return (false, new { result.Data }); } } private Task GetModelAsync(IAppEntity app) { var cacheKey = CreateCacheKey(app.Id, app.Version.ToString()); return Cache.GetOrCreateAsync(cacheKey, async entry => { entry.AbsoluteExpirationRelativeToNow = CacheDuration; var allSchemas = await appProvider.GetSchemasAsync(app.Id); return new GraphQLModel(app, allSchemas, urlGenerator); }); } private static object CreateCacheKey(Guid appId, string etag) { return $"GraphQLModel_{appId}_{etag}"; } } }