c084efa78e
Mint a first-party localStorage anonId, thread it onto play/share beacons, persist it via EventController, and add all-time distinct-listener counts (site/track/release). Storage columns + indexes already existed from 16.1.
101 lines
3.7 KiB
C#
101 lines
3.7 KiB
C#
using DeepDrftData.Repositories;
|
|
using DeepDrftModels.Enums;
|
|
using Microsoft.Extensions.Logging;
|
|
using NetBlocks.Models;
|
|
|
|
namespace DeepDrftData;
|
|
|
|
/// <summary>
|
|
/// <see cref="IEventService"/> implementation over <see cref="EventRepository"/>. The layer boundary
|
|
/// matches the rest of DeepDrftData: the repository owns the EF constructs and the write transaction;
|
|
/// this service catches at the boundary and returns a NetBlocks <see cref="Result"/>. Telemetry is
|
|
/// best-effort by design (§2.2) — a failed write is logged and surfaced as a fail result, never thrown
|
|
/// at the caller, so a telemetry hiccup can never reach a listener.
|
|
/// </summary>
|
|
public class EventManager : IEventService
|
|
{
|
|
private readonly EventRepository _repository;
|
|
private readonly ILogger<EventManager> _logger;
|
|
|
|
public EventManager(EventRepository repository, ILogger<EventManager> logger)
|
|
{
|
|
_repository = repository;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<Result> RecordPlay(
|
|
string trackEntryKey, PlayBucket bucket, string? anonId = null, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await _repository.RecordPlayAsync(trackEntryKey, bucket, anonId, cancellationToken);
|
|
return Result.CreatePassResult();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError(e, "Failed to record play event for track {TrackEntryKey}", trackEntryKey);
|
|
return Result.CreateFailResult(e.Message);
|
|
}
|
|
}
|
|
|
|
public async Task<Result> RecordShare(
|
|
ShareTargetType targetType, string targetKey, ShareChannel channel, string? anonId = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
await _repository.RecordShareAsync(targetType, targetKey, channel, anonId, cancellationToken);
|
|
return Result.CreatePassResult();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError(e, "Failed to record share event for {TargetType} {TargetKey}", targetType, targetKey);
|
|
return Result.CreateFailResult(e.Message);
|
|
}
|
|
}
|
|
|
|
public async Task<ResultContainer<int>> GetDistinctListenerCount(CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var count = await _repository.CountDistinctListenersAsync(cancellationToken);
|
|
return ResultContainer<int>.CreatePassResult(count);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError(e, "Failed to count distinct listeners");
|
|
return ResultContainer<int>.CreateFailResult(e.Message);
|
|
}
|
|
}
|
|
|
|
public async Task<ResultContainer<int>> GetDistinctListenerCountForTrack(
|
|
string trackEntryKey, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var count = await _repository.CountDistinctListenersForTrackAsync(trackEntryKey, cancellationToken);
|
|
return ResultContainer<int>.CreatePassResult(count);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError(e, "Failed to count distinct listeners for track {TrackEntryKey}", trackEntryKey);
|
|
return ResultContainer<int>.CreateFailResult(e.Message);
|
|
}
|
|
}
|
|
|
|
public async Task<ResultContainer<int>> GetDistinctListenerCountForRelease(
|
|
long releaseId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var count = await _repository.CountDistinctListenersForReleaseAsync(releaseId, cancellationToken);
|
|
return ResultContainer<int>.CreatePassResult(count);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError(e, "Failed to count distinct listeners for release {ReleaseId}", releaseId);
|
|
return ResultContainer<int>.CreateFailResult(e.Message);
|
|
}
|
|
}
|
|
}
|