32 lines
1.6 KiB
C#
32 lines
1.6 KiB
C#
#nullable enable
|
|
using Baya.Application.Contracts.Reviews;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace Baya.Infrastructure.CrossCutting.Seams;
|
|
|
|
/// <summary>
|
|
/// Deterministic mock <see cref="IReviewModerationService"/> (b14) — a keyword filter / pass-through with no
|
|
/// external call. A banned-word hit → <see cref="ModerationDecision.Reject"/>; otherwise clean text → a
|
|
/// human-review <see cref="ModerationDecision.Flag"/> by default (so the publish gate holds), or
|
|
/// <see cref="ModerationDecision.Approve"/> when <see cref="ReviewModerationOptions.AutoApproveClean"/> is set.
|
|
/// The real text classifier / LLM endpoint swaps in by a registration change only — the moderation command
|
|
/// keeps decision authority and the human override, so it never touches the handler.
|
|
/// </summary>
|
|
public sealed class MockReviewModerationService(IOptions<SeamOptions> options) : IReviewModerationService
|
|
{
|
|
private readonly ReviewModerationOptions _options = options.Value.ReviewModeration;
|
|
|
|
public ValueTask<ModerationVerdict> ScreenAsync(string? reviewText, CancellationToken cancellationToken = default)
|
|
{
|
|
var text = reviewText?.Trim() ?? string.Empty;
|
|
|
|
var hit = _options.BannedWords.FirstOrDefault(
|
|
w => !string.IsNullOrWhiteSpace(w) && text.Contains(w, StringComparison.OrdinalIgnoreCase));
|
|
if (hit is not null)
|
|
return ValueTask.FromResult(new ModerationVerdict(ModerationDecision.Reject, $"banned_word:{hit}"));
|
|
|
|
var decision = _options.AutoApproveClean ? ModerationDecision.Approve : ModerationDecision.Flag;
|
|
return ValueTask.FromResult(new ModerationVerdict(decision, "clean"));
|
|
}
|
|
}
|