mirror of https://github.com/Squidex/squidex.git
38 changed files with 579 additions and 569 deletions
@ -1,40 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using MongoDB.Bson; |
|
||||
using MongoDB.Bson.Serialization.Attributes; |
|
||||
using Squidex.Domain.Apps.Entities.Assets; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets |
|
||||
{ |
|
||||
public sealed class MongoAssetStatsEntity : IAssetStatsEntity |
|
||||
{ |
|
||||
[BsonId] |
|
||||
[BsonElement] |
|
||||
[BsonRepresentation(BsonType.String)] |
|
||||
public string Id { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
[BsonRepresentation(BsonType.String)] |
|
||||
public Guid AssetId { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
[BsonDateTimeOptions(DateOnly = true)] |
|
||||
public DateTime Date { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public long TotalSize { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public long TotalCount { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,101 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using MongoDB.Driver; |
|
||||
using Squidex.Domain.Apps.Entities.Assets; |
|
||||
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.EventSourcing; |
|
||||
using Squidex.Infrastructure.MongoDb; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets |
|
||||
{ |
|
||||
public partial class MongoAssetStatsRepository : MongoRepositoryBase<MongoAssetStatsEntity>, IAssetStatsRepository, IEventConsumer |
|
||||
{ |
|
||||
public MongoAssetStatsRepository(IMongoDatabase database) |
|
||||
: base(database) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override string CollectionName() |
|
||||
{ |
|
||||
return "Projections_AssetStats"; |
|
||||
} |
|
||||
|
|
||||
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection, CancellationToken ct = default(CancellationToken)) |
|
||||
{ |
|
||||
return collection.Indexes.CreateManyAsync( |
|
||||
new[] |
|
||||
{ |
|
||||
new CreateIndexModel<MongoAssetStatsEntity>(Index.Ascending(x => x.AssetId).Ascending(x => x.Date)), |
|
||||
new CreateIndexModel<MongoAssetStatsEntity>(Index.Ascending(x => x.AssetId).Descending(x => x.Date)) |
|
||||
}, ct); |
|
||||
} |
|
||||
|
|
||||
public async Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate) |
|
||||
{ |
|
||||
var originalSizesEntities = |
|
||||
await Collection.Find(x => x.AssetId == appId && x.Date >= fromDate && x.Date <= toDate).SortBy(x => x.Date) |
|
||||
.ToListAsync(); |
|
||||
|
|
||||
var enrichedSizes = new List<MongoAssetStatsEntity>(); |
|
||||
|
|
||||
var sizesDictionary = originalSizesEntities.ToDictionary(x => x.Date); |
|
||||
|
|
||||
var previousSize = long.MinValue; |
|
||||
var previousCount = long.MinValue; |
|
||||
|
|
||||
for (var date = fromDate; date <= toDate; date = date.AddDays(1)) |
|
||||
{ |
|
||||
var size = sizesDictionary.GetOrDefault(date); |
|
||||
|
|
||||
if (size != null) |
|
||||
{ |
|
||||
previousSize = size.TotalSize; |
|
||||
previousCount = size.TotalCount; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
if (previousSize < 0) |
|
||||
{ |
|
||||
var firstBeforeRangeEntity = |
|
||||
await Collection.Find(x => x.AssetId == appId && x.Date < fromDate).SortByDescending(x => x.Date) |
|
||||
.FirstOrDefaultAsync(); |
|
||||
|
|
||||
previousSize = firstBeforeRangeEntity?.TotalSize ?? 0L; |
|
||||
previousCount = firstBeforeRangeEntity?.TotalCount ?? 0L; |
|
||||
} |
|
||||
|
|
||||
size = new MongoAssetStatsEntity |
|
||||
{ |
|
||||
Date = date, |
|
||||
TotalSize = previousSize, |
|
||||
TotalCount = previousCount |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
enrichedSizes.Add(size); |
|
||||
} |
|
||||
|
|
||||
return enrichedSizes; |
|
||||
} |
|
||||
|
|
||||
public async Task<long> GetTotalSizeAsync(Guid appId) |
|
||||
{ |
|
||||
var totalSizeEntity = |
|
||||
await Collection.Find(x => x.AssetId == appId).SortByDescending(x => x.Date) |
|
||||
.FirstOrDefaultAsync(); |
|
||||
|
|
||||
return totalSizeEntity?.TotalSize ?? 0; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,72 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using MongoDB.Bson; |
|
||||
using MongoDB.Bson.Serialization.Attributes; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.MongoDb; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.MongoDb.History |
|
||||
{ |
|
||||
public sealed class MongoHistoryEventEntity : MongoEntity, |
|
||||
IEntity, |
|
||||
IUpdateableEntity, |
|
||||
IUpdateableEntityWithVersion, |
|
||||
IUpdateableEntityWithCreatedBy |
|
||||
{ |
|
||||
[BsonElement] |
|
||||
[BsonRequired] |
|
||||
[BsonRepresentation(BsonType.String)] |
|
||||
public Guid AppId { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public long Version { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public string Channel { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public string Message { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public RefToken Actor { get; set; } |
|
||||
|
|
||||
[BsonRequired] |
|
||||
[BsonElement] |
|
||||
public Dictionary<string, string> Parameters { get; set; } |
|
||||
|
|
||||
RefToken IUpdateableEntityWithCreatedBy.CreatedBy |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
return Actor; |
|
||||
} |
|
||||
set |
|
||||
{ |
|
||||
Actor = value; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public MongoHistoryEventEntity() |
|
||||
{ |
|
||||
Parameters = new Dictionary<string, string>(); |
|
||||
} |
|
||||
|
|
||||
public MongoHistoryEventEntity AddParameter(string key, string value) |
|
||||
{ |
|
||||
Parameters.Add(key, value); |
|
||||
|
|
||||
return this; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,67 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Entities.Assets.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.UsageTracking; |
||||
|
|
||||
|
#pragma warning disable CS0649
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Assets |
||||
|
{ |
||||
|
public partial class DefaultAssetStatsRepository : IAssetStatsRepository |
||||
|
{ |
||||
|
private const string Category = "Default"; |
||||
|
private const string CounterTotalCount = "TotalAssets"; |
||||
|
private const string CounterTotalSize = "TotalSize"; |
||||
|
private static readonly DateTime SummaryDate; |
||||
|
private readonly IUsageRepository usageStore; |
||||
|
|
||||
|
public DefaultAssetStatsRepository(IUsageRepository usageStore) |
||||
|
{ |
||||
|
Guard.NotNull(usageStore, nameof(usageStore)); |
||||
|
|
||||
|
this.usageStore = usageStore; |
||||
|
} |
||||
|
|
||||
|
public async Task<long> GetTotalSizeAsync(Guid appId) |
||||
|
{ |
||||
|
var entries = await usageStore.QueryAsync(appId.ToString(), SummaryDate, SummaryDate); |
||||
|
|
||||
|
return (long)entries.Select(x => x.Counters.Get(CounterTotalSize)).FirstOrDefault(); |
||||
|
} |
||||
|
|
||||
|
public async Task<IReadOnlyList<AssetStats>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate) |
||||
|
{ |
||||
|
var enriched = new List<AssetStats>(); |
||||
|
|
||||
|
var usagesFlat = await usageStore.QueryAsync(appId.ToString(), fromDate, toDate); |
||||
|
|
||||
|
for (var date = fromDate; date <= toDate; date = date.AddDays(1)) |
||||
|
{ |
||||
|
var stored = usagesFlat.FirstOrDefault(x => x.Date == date && x.Category == Category); |
||||
|
|
||||
|
var totalCount = 0L; |
||||
|
var totalSize = 0L; |
||||
|
|
||||
|
if (stored != null) |
||||
|
{ |
||||
|
totalCount = (long)stored.Counters.Get(CounterTotalCount); |
||||
|
totalSize = (long)stored.Counters.Get(CounterTotalSize); |
||||
|
} |
||||
|
|
||||
|
enriched.Add(new AssetStats(date, totalCount, totalSize)); |
||||
|
} |
||||
|
|
||||
|
return enriched; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using NodaTime; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.History |
||||
|
{ |
||||
|
public sealed class HistoryEvent |
||||
|
{ |
||||
|
public Guid Id { get; set; } = Guid.NewGuid(); |
||||
|
|
||||
|
public Guid AppId { get; set; } |
||||
|
|
||||
|
public RefToken Actor { get; set; } |
||||
|
|
||||
|
public Instant Created { get; set; } |
||||
|
|
||||
|
public long Version { get; set; } |
||||
|
|
||||
|
public string Channel { get; set; } |
||||
|
|
||||
|
public string Message { get; set; } |
||||
|
|
||||
|
public Dictionary<string, string> Parameters { get; set; } = new Dictionary<string, string>(); |
||||
|
|
||||
|
public HistoryEvent() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public HistoryEvent(string channel, string message) |
||||
|
{ |
||||
|
Guard.NotNullOrEmpty(channel, nameof(channel)); |
||||
|
Guard.NotNullOrEmpty(message, nameof(message)); |
||||
|
|
||||
|
Channel = channel; |
||||
|
|
||||
|
Message = message; |
||||
|
} |
||||
|
|
||||
|
public HistoryEvent AddParameter(string key, object value) |
||||
|
{ |
||||
|
Parameters[key] = value.ToString(); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,43 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.History |
|
||||
{ |
|
||||
public sealed class HistoryEventToStore |
|
||||
{ |
|
||||
private readonly Dictionary<string, string> parameters = new Dictionary<string, string>(); |
|
||||
|
|
||||
public string Channel { get; } |
|
||||
|
|
||||
public string Message { get; } |
|
||||
|
|
||||
public IReadOnlyDictionary<string, string> Parameters |
|
||||
{ |
|
||||
get { return parameters; } |
|
||||
} |
|
||||
|
|
||||
public HistoryEventToStore(string channel, string message) |
|
||||
{ |
|
||||
Guard.NotNullOrEmpty(channel, nameof(channel)); |
|
||||
Guard.NotNullOrEmpty(message, nameof(message)); |
|
||||
|
|
||||
Channel = channel; |
|
||||
|
|
||||
Message = message; |
|
||||
} |
|
||||
|
|
||||
public HistoryEventToStore AddParameter(string key, object value) |
|
||||
{ |
|
||||
parameters[key] = value.ToString(); |
|
||||
|
|
||||
return this; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,89 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Entities.History.Repositories; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.History |
||||
|
{ |
||||
|
public sealed class HistoryService : IHistoryService, IEventConsumer |
||||
|
{ |
||||
|
private readonly Dictionary<string, string> texts = new Dictionary<string, string>(); |
||||
|
private readonly List<IHistoryEventsCreator> creators; |
||||
|
private readonly IHistoryEventRepository repository; |
||||
|
private readonly IClock clock; |
||||
|
|
||||
|
public string Name |
||||
|
{ |
||||
|
get { return GetType().Name; } |
||||
|
} |
||||
|
|
||||
|
public string EventsFilter |
||||
|
{ |
||||
|
get { return ".*"; } |
||||
|
} |
||||
|
|
||||
|
public HistoryService(IHistoryEventRepository repository, IEnumerable<IHistoryEventsCreator> creators, IClock clock) |
||||
|
{ |
||||
|
Guard.NotNull(repository, nameof(repository)); |
||||
|
Guard.NotNull(clock, nameof(clock)); |
||||
|
Guard.NotNull(creators, nameof(creators)); |
||||
|
|
||||
|
this.clock = clock; |
||||
|
this.creators = creators.ToList(); |
||||
|
|
||||
|
foreach (var creator in this.creators) |
||||
|
{ |
||||
|
foreach (var text in creator.Texts) |
||||
|
{ |
||||
|
texts[text.Key] = text.Value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.repository = repository; |
||||
|
} |
||||
|
|
||||
|
public async Task On(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
foreach (var creator in creators) |
||||
|
{ |
||||
|
var historyEvent = await creator.CreateEventAsync(@event); |
||||
|
|
||||
|
if (historyEvent != null) |
||||
|
{ |
||||
|
var appEvent = (AppEvent)@event.Payload; |
||||
|
|
||||
|
historyEvent.Actor = appEvent.Actor; |
||||
|
historyEvent.AppId = appEvent.AppId.Id; |
||||
|
historyEvent.Created = clock.GetCurrentInstant(); |
||||
|
historyEvent.Version = @event.Headers.EventStreamNumber(); |
||||
|
|
||||
|
await repository.InsertAsync(historyEvent); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync() |
||||
|
{ |
||||
|
return repository.ClearAsync(); |
||||
|
} |
||||
|
|
||||
|
public async Task<IReadOnlyList<ParsedHistoryEvent>> QueryByChannelAsync(Guid appId, string channelPrefix, int count) |
||||
|
{ |
||||
|
var items = await repository.QueryByChannelAsync(appId, channelPrefix, count); |
||||
|
|
||||
|
return items.Select(x => new ParsedHistoryEvent(x, texts)).ToList(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,23 +1,18 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
// All rights reserved. Licensed under the MIT license.
|
// All rights reserved. Licensed under the MIT license.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
using System; |
using System; |
||||
using Squidex.Infrastructure; |
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.History |
namespace Squidex.Domain.Apps.Entities.History |
||||
{ |
{ |
||||
public interface IHistoryEventEntity : IEntity |
public interface IHistoryService |
||||
{ |
{ |
||||
Guid EventId { get; } |
Task<IReadOnlyList<ParsedHistoryEvent>> QueryByChannelAsync(Guid appId, string channelPrefix, int count); |
||||
|
|
||||
RefToken Actor { get; } |
|
||||
|
|
||||
string Message { get; } |
|
||||
|
|
||||
long Version { get; } |
|
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,96 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Driver; |
||||
|
using Squidex.Infrastructure.MongoDb; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.UsageTracking |
||||
|
{ |
||||
|
public sealed class MongoUsageRepository : MongoRepositoryBase<MongoUsage>, IUsageRepository |
||||
|
{ |
||||
|
private static readonly BulkWriteOptions Unordered = new BulkWriteOptions { IsOrdered = false }; |
||||
|
|
||||
|
public MongoUsageRepository(IMongoDatabase database) |
||||
|
: base(database) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override string CollectionName() |
||||
|
{ |
||||
|
return "UsagesV2"; |
||||
|
} |
||||
|
|
||||
|
protected override Task SetupCollectionAsync(IMongoCollection<MongoUsage> collection, CancellationToken ct = default(CancellationToken)) |
||||
|
{ |
||||
|
return collection.Indexes.CreateOneAsync( |
||||
|
new CreateIndexModel<MongoUsage>(Index.Ascending(x => x.Key).Ascending(x => x.Category).Ascending(x => x.Date)), cancellationToken: ct); |
||||
|
} |
||||
|
|
||||
|
public async Task TrackUsagesAsync(params UsageUpdate[] updates) |
||||
|
{ |
||||
|
if (updates.Length == 1) |
||||
|
{ |
||||
|
var value = updates[0]; |
||||
|
|
||||
|
if (value.Counters.Count > 0) |
||||
|
{ |
||||
|
var (filter, update) = CreateOperation(value); |
||||
|
|
||||
|
await Collection.UpdateOneAsync(filter, update, Upsert); |
||||
|
} |
||||
|
} |
||||
|
else if (updates.Length > 0) |
||||
|
{ |
||||
|
var writes = new List<WriteModel<MongoUsage>>(); |
||||
|
|
||||
|
foreach (var value in updates) |
||||
|
{ |
||||
|
if (value.Counters.Count > 0) |
||||
|
{ |
||||
|
var (filter, update) = CreateOperation(value); |
||||
|
|
||||
|
writes.Add(new UpdateOneModel<MongoUsage>(filter, update) { IsUpsert = true }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await Collection.BulkWriteAsync(writes, Unordered); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static (FilterDefinition<MongoUsage>, UpdateDefinition<MongoUsage>) CreateOperation(UsageUpdate usageUpdate) |
||||
|
{ |
||||
|
var id = $"{usageUpdate.Key}_{usageUpdate.Date:yyyy-MM-dd}_{usageUpdate.Category}"; |
||||
|
|
||||
|
var update = Update |
||||
|
.SetOnInsert(x => x.Id, id) |
||||
|
.SetOnInsert(x => x.Key, usageUpdate.Key) |
||||
|
.SetOnInsert(x => x.Date, usageUpdate.Date) |
||||
|
.SetOnInsert(x => x.Category, usageUpdate.Category); |
||||
|
|
||||
|
foreach (var counter in usageUpdate.Counters) |
||||
|
{ |
||||
|
update = update.Inc($"Counters.{counter.Key}", counter.Value); |
||||
|
} |
||||
|
|
||||
|
var filter = Filter.Eq(x => x.Id, id); |
||||
|
|
||||
|
return (filter, update); |
||||
|
} |
||||
|
|
||||
|
public async Task<IReadOnlyList<StoredUsage>> QueryAsync(string key, DateTime fromDate, DateTime toDate) |
||||
|
{ |
||||
|
var entities = await Collection.Find(x => x.Key == key && x.Date >= fromDate && x.Date <= toDate).ToListAsync(); |
||||
|
|
||||
|
return entities.Select(x => new StoredUsage(x.Category, x.Date, x.Counters)).ToList(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,58 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using MongoDB.Driver; |
|
||||
using Squidex.Infrastructure.MongoDb; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.UsageTracking |
|
||||
{ |
|
||||
public sealed class MongoUsageStore : MongoRepositoryBase<MongoUsage>, IUsageStore |
|
||||
{ |
|
||||
public MongoUsageStore(IMongoDatabase database) |
|
||||
: base(database) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override string CollectionName() |
|
||||
{ |
|
||||
return "Usages"; |
|
||||
} |
|
||||
|
|
||||
protected override Task SetupCollectionAsync(IMongoCollection<MongoUsage> collection, CancellationToken ct = default(CancellationToken)) |
|
||||
{ |
|
||||
return collection.Indexes.CreateOneAsync( |
|
||||
new CreateIndexModel<MongoUsage>(Index.Ascending(x => x.Key).Ascending(x => x.Category).Ascending(x => x.Date)), cancellationToken: ct); |
|
||||
} |
|
||||
|
|
||||
public Task TrackUsagesAsync(DateTime date, string key, string category, double count, double elapsedMs) |
|
||||
{ |
|
||||
var id = $"{key}_{date:yyyy-MM-dd}_{category}"; |
|
||||
|
|
||||
return Collection.UpdateOneAsync(x => x.Id == id && x.Category == category, |
|
||||
Update |
|
||||
.Inc(x => x.TotalCount, count) |
|
||||
.Inc(x => x.TotalElapsedMs, elapsedMs) |
|
||||
.SetOnInsert(x => x.Id, id) |
|
||||
.SetOnInsert(x => x.Key, key) |
|
||||
.SetOnInsert(x => x.Date, date) |
|
||||
.SetOnInsert(x => x.Category, category), |
|
||||
Upsert); |
|
||||
} |
|
||||
|
|
||||
public async Task<IReadOnlyList<StoredUsage>> QueryAsync(string key, DateTime fromDate, DateTime toDate) |
|
||||
{ |
|
||||
var entities = await Collection.Find(x => x.Key == key && x.Date >= fromDate && x.Date <= toDate).ToListAsync(); |
|
||||
|
|
||||
return entities.Select(x => new StoredUsage(x.Category, x.Date, (long)x.TotalCount, (long)x.TotalElapsedMs)).ToList(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,26 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.UsageTracking |
||||
|
{ |
||||
|
public sealed class Counters : Dictionary<string, double> |
||||
|
{ |
||||
|
public double Get(string name) |
||||
|
{ |
||||
|
if (name == null) |
||||
|
{ |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
TryGetValue(name, out var value); |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.UsageTracking |
||||
|
{ |
||||
|
public struct UsageUpdate |
||||
|
{ |
||||
|
public DateTime Date; |
||||
|
|
||||
|
public string Key; |
||||
|
|
||||
|
public string Category; |
||||
|
|
||||
|
public Counters Counters; |
||||
|
|
||||
|
public UsageUpdate(DateTime date, string key, string category, Counters counters) |
||||
|
{ |
||||
|
Key = key; |
||||
|
Category = category; |
||||
|
Counters = counters; |
||||
|
Date = date; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue