feat(phase-16): anonymous play & share telemetry substrate (wave 16.1)
Player-service play-session tracker (floor + 3-bucket classify), SharePopover share tracker with debounce, sendBeacon interop, proxied rate-limited POST api/event/{play,share}, append-only event logs + incremental play_counter with server-side release resolution. Migration authored, not applied. No anonId, no read surface.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user