Add rebus (#1)
Reviewed-on: #1 Co-authored-by: Sergey Nazarov <insight.appdev@gmail.com> Co-committed-by: Sergey Nazarov <insight.appdev@gmail.com>
This commit is contained in:
parent
98f56ed212
commit
2692f549f4
@ -1,16 +1,34 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<MicrosoftVersion>8.0.0</MicrosoftVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Serilog">
|
||||
<PackageVersion Include="Serilog" Version="3.1.1"/>
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1"/>
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Microsoft">
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftVersion)"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<MicrosoftVersion>8.0.0</MicrosoftVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Serilog">
|
||||
<PackageVersion Include="Nocr.TL.2024.03.21.001.rc1" Version="0.0.1" />
|
||||
<PackageVersion Include="RestEase" Version="1.6.4" />
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Rebus">
|
||||
<PackageVersion Include="Rebus" Version="8.2.2" />
|
||||
<PackageVersion Include="Rebus.ServiceProvider" Version="10.1.0" />
|
||||
<PackageVersion Include="Rebus.RabbitMq" Version="9.0.1" />
|
||||
<PackageVersion Include="Rebus.Serilog" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Microsoft">
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftVersion)" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="$(MicrosoftVersion)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Tests">
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
|
||||
<PackageVersion Include="xunit" Version="2.4.2"/>
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5"/>
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.0"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -14,6 +14,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.AppService
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.Host", "src\Nocr.TextMatcher.Host\Nocr.TextMatcher.Host.csproj", "{58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.Async.Api.Contracts", "src\Nocr.TextMatcher.Async.Api.Contracts\Nocr.TextMatcher.Async.Api.Contracts.csproj", "{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.Api.Contracts", "src\Nocr.TextMatcher.Api.Contracts\Nocr.TextMatcher.Api.Contracts.csproj", "{A6332064-40EE-498A-826D-638BC295A185}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.AppServices.Contracts", "src\Nocr.TextMatcher.AppServices.Contracts\Nocr.TextMatcher.AppServices.Contracts.csproj", "{01BF99EE-D635-4931-9CAB-B51C54F0760B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Tests", "_Tests", "{6E4D9F75-861F-4C00-A5C8-00D1BEE5A659}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nocr.TextMatcher.AppServices.UnitTests", "tests\Nocr.TextMatcher.AppServices.UnitTests\Nocr.TextMatcher.AppServices.UnitTests.csproj", "{B721E055-84AF-44C6-973D-33241FD2EA7C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -32,5 +42,24 @@ Global
|
||||
{58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{58D5C9FD-75A9-4FFB-9FBD-BE8E9FCE3016}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4028666B-FAE8-4DB9-8D10-B00F5F31BDCC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A6332064-40EE-498A-826D-638BC295A185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A6332064-40EE-498A-826D-638BC295A185}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A6332064-40EE-498A-826D-638BC295A185}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A6332064-40EE-498A-826D-638BC295A185}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{01BF99EE-D635-4931-9CAB-B51C54F0760B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{01BF99EE-D635-4931-9CAB-B51C54F0760B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{01BF99EE-D635-4931-9CAB-B51C54F0760B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{01BF99EE-D635-4931-9CAB-B51C54F0760B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B721E055-84AF-44C6-973D-33241FD2EA7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B721E055-84AF-44C6-973D-33241FD2EA7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B721E055-84AF-44C6-973D-33241FD2EA7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B721E055-84AF-44C6-973D-33241FD2EA7C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{B721E055-84AF-44C6-973D-33241FD2EA7C} = {6E4D9F75-861F-4C00-A5C8-00D1BEE5A659}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
2
Nocr.TextMatcher.sln.DotSettings
Normal file
2
Nocr.TextMatcher.sln.DotSettings
Normal file
@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nocr/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Nocr.TextMatcher.AppServices.Contracts\Nocr.TextMatcher.AppServices.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RestEase" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,21 @@
|
||||
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
|
||||
using RestEase;
|
||||
|
||||
namespace Nocr.TextMatcher.Api.Contracts.TextMatches.Dto;
|
||||
|
||||
public class CreateTextMatchRequest
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
|
||||
public long ChatId { get; set; }
|
||||
|
||||
public string Template { get; set; }
|
||||
|
||||
public TextMatchRule Rule { get; set; }
|
||||
}
|
||||
|
||||
[BasePath(WebRoutes.TextMatches.Path)]
|
||||
public interface ITextMatcherController
|
||||
{
|
||||
Task<long> Create([Body] CreateTextMatchRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
11
src/Nocr.TextMatcher.Api.Contracts/WebRoutes.cs
Normal file
11
src/Nocr.TextMatcher.Api.Contracts/WebRoutes.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Nocr.TextMatcher.Api.Contracts;
|
||||
|
||||
public static class WebRoutes
|
||||
{
|
||||
public const string BasePath = "/api";
|
||||
|
||||
public static class TextMatches
|
||||
{
|
||||
public const string Path = BasePath + "/" + "text-matches";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,13 @@
|
||||
namespace Nocr.TextMatcher.AppServices.Contracts.TextMatches;
|
||||
|
||||
public enum TextMatchRule
|
||||
{
|
||||
// Substring
|
||||
Full = 1,
|
||||
|
||||
// Any word from list splited by space
|
||||
AnyWord,
|
||||
|
||||
// All words from list splited by space
|
||||
AllWords
|
||||
}
|
||||
@ -2,9 +2,14 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Nocr.TL.2024.03.21.001.rc1" />
|
||||
<PackageReference Include="Rebus" />
|
||||
<PackageReference Include="Rebus.ServiceProvider" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Nocr.TextMatcher.AppServices.Contracts\Nocr.TextMatcher.AppServices.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Nocr.TextMatcher.Async.Api.Contracts\Nocr.TextMatcher.Async.Api.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Nocr.TextMatcher.Core\Nocr.TextMatcher.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Nocr.TextMatcher.AppServices.TextMatchers;
|
||||
using Nocr.TextMatcher.AppServices.TextMatches.Repositories;
|
||||
using Nocr.TextMatcher.AppServices.TextMatches.Services;
|
||||
using Rebus.Config;
|
||||
|
||||
namespace Nocr.TextMatcher.AppServices;
|
||||
|
||||
@ -10,6 +14,9 @@ public static class ServiceCollectionExtensions
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
|
||||
// Add registrations here
|
||||
services.AddRebusHandler<MessageReceivedHandler>();
|
||||
services.AddSingleton<ITextMatchService, TextMatchService>();
|
||||
services.AddSingleton<ITextMatchRepository, InMemoryTextMatchRepository>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Nocr.TelegramListener.Async.Api.Contracts.Events;
|
||||
using Nocr.TextMatcher.AppServices.TextMatches.Services;
|
||||
using Nocr.TextMatcher.Async.Api.Contracts;
|
||||
using Nocr.TextMatcher.Core.Dates;
|
||||
using Rebus.Bus;
|
||||
using Rebus.Handlers;
|
||||
|
||||
namespace Nocr.TextMatcher.AppServices.TextMatchers;
|
||||
|
||||
public sealed class MessageReceivedHandler : IHandleMessages<MessageReceived>
|
||||
{
|
||||
private readonly ILogger<MessageReceivedHandler> _logger;
|
||||
private readonly IBus _bus;
|
||||
private readonly ITextMatchService _textMatchService;
|
||||
private readonly ICurrentDateProvider _dateProvider;
|
||||
|
||||
public MessageReceivedHandler(ILogger<MessageReceivedHandler> logger,
|
||||
IBus bus,
|
||||
ITextMatchService textMatchService,
|
||||
ICurrentDateProvider dateProvider)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_bus = bus ?? throw new ArgumentNullException(nameof(bus));
|
||||
_textMatchService = textMatchService ?? throw new ArgumentNullException(nameof(textMatchService));
|
||||
_dateProvider = dateProvider ?? throw new ArgumentNullException(nameof(dateProvider));
|
||||
}
|
||||
|
||||
public async Task Handle(MessageReceived message)
|
||||
{
|
||||
_logger.LogInformation("Received message: {@Message}", message);
|
||||
|
||||
var matches = await _textMatchService.Get();
|
||||
|
||||
foreach (var match in matches)
|
||||
{
|
||||
if (match.IsMatches(message.ChatId, message.Text))
|
||||
{
|
||||
_logger.LogInformation("Message {@Message} matched {@Match}", message, match);
|
||||
var @event = new TextMatchMatched
|
||||
{
|
||||
ChatId = message.ChatId,
|
||||
Text = message.Text,
|
||||
UserId = message.From,
|
||||
OccuredDateTime = message.OccuredDateTime,
|
||||
PublishedDateTime = _dateProvider.UtcNow
|
||||
};
|
||||
|
||||
await _bus.Advanced.Topics.Publish("nocr.text.matcher", @event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
using Nocr.TextMatcher.AppServices.TextMatches.Services;
|
||||
|
||||
namespace Nocr.TextMatcher.AppServices.TextMatches.Repositories;
|
||||
|
||||
public interface ITextMatchRepository
|
||||
{
|
||||
Task<long> Create(TextMatch textMatch, CancellationToken cancellationToken = default);
|
||||
|
||||
Task Delete(long id, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IReadOnlyCollection<TextMatch>> Get(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
namespace Nocr.TextMatcher.AppServices.TextMatches.Repositories;
|
||||
|
||||
public sealed class InMemoryTextMatchRepository : ITextMatchRepository
|
||||
{
|
||||
private long _id = 0;
|
||||
|
||||
private List<TextMatch> _textMatches = new List<TextMatch>();
|
||||
|
||||
public Task<long> Create(TextMatch textMatch, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var id = Interlocked.Increment(ref _id);
|
||||
textMatch.Id = id;
|
||||
_textMatches.Add(textMatch);
|
||||
|
||||
return Task.FromResult(id);
|
||||
}
|
||||
|
||||
public Task Delete(long id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var index = _textMatches.FindIndex(x => x.Id == id);
|
||||
_textMatches.RemoveAt(index);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IReadOnlyCollection<TextMatch>> Get(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult<IReadOnlyCollection<TextMatch>>(_textMatches);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
|
||||
|
||||
namespace Nocr.TextMatcher.AppServices.TextMatches.Services;
|
||||
|
||||
public interface ITextMatchService
|
||||
{
|
||||
Task<long> Create(long userId, long chatId, string template, TextMatchRule rule, CancellationToken cancellationToken = default);
|
||||
|
||||
Task Delete(long id, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IReadOnlyCollection<TextMatch>> Get(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
|
||||
using Nocr.TextMatcher.AppServices.TextMatches.Repositories;
|
||||
using Nocr.TextMatcher.Async.Api.Contracts;
|
||||
using Nocr.TextMatcher.Core.Dates;
|
||||
using Rebus.Bus;
|
||||
|
||||
namespace Nocr.TextMatcher.AppServices.TextMatches.Services;
|
||||
|
||||
public sealed class TextMatchService : ITextMatchService
|
||||
{
|
||||
private readonly IBus _bus;
|
||||
private readonly ITextMatchRepository _repository;
|
||||
private readonly ICurrentDateProvider _dateProvider;
|
||||
|
||||
public TextMatchService(IBus bus, ITextMatchRepository repository,
|
||||
ICurrentDateProvider dateProvider)
|
||||
{
|
||||
_bus = bus ?? throw new ArgumentNullException(nameof(bus));
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
_dateProvider = dateProvider ?? throw new ArgumentNullException(nameof(dateProvider));
|
||||
}
|
||||
|
||||
public async Task<long> Create(long userId, long chatId, string template, TextMatchRule rule,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var textMatch = TextMatch.Initialize(userId, chatId, template, rule, _dateProvider.UtcNow);
|
||||
await _repository.Create(textMatch, cancellationToken);
|
||||
|
||||
var @event = new TextMatchCreated
|
||||
{
|
||||
ChatId = chatId
|
||||
};
|
||||
|
||||
await _bus.Advanced.Topics.Publish("nocr.text.matcher", @event);
|
||||
|
||||
return textMatch.Id;
|
||||
}
|
||||
|
||||
public Task Delete(long id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _repository.Delete(id, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyCollection<TextMatch>> Get(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _repository.Get(cancellationToken);
|
||||
}
|
||||
}
|
||||
70
src/Nocr.TextMatcher.AppServices/TextMatches/TextMatch.cs
Normal file
70
src/Nocr.TextMatcher.AppServices/TextMatches/TextMatch.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
|
||||
|
||||
namespace Nocr.TextMatcher.AppServices.TextMatches;
|
||||
|
||||
public sealed class TextMatch
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public long UserId { get; private set; }
|
||||
|
||||
public long ChatId { get; private set; }
|
||||
|
||||
public string Template { get; private set; }
|
||||
|
||||
public TextMatchRule Rule { get; private set; }
|
||||
|
||||
public DateTimeOffset CreatedDateTime { get; private set; }
|
||||
|
||||
private TextMatch(long userId,
|
||||
long chatId,
|
||||
string template,
|
||||
TextMatchRule rule,
|
||||
DateTimeOffset createdDateTime)
|
||||
{
|
||||
UserId = userId;
|
||||
ChatId = chatId;
|
||||
Template = template;
|
||||
Rule = rule;
|
||||
CreatedDateTime = createdDateTime;
|
||||
}
|
||||
|
||||
public static TextMatch Initialize(long userId,
|
||||
long chatId,
|
||||
string template,
|
||||
TextMatchRule rule,
|
||||
DateTimeOffset createdDateTime)
|
||||
{
|
||||
if (userId <= 0)
|
||||
throw new ArgumentException("User id should be greater tha 0", nameof(userId));
|
||||
|
||||
if (chatId <= 0)
|
||||
throw new ArgumentException("Chat id should be greater tha 0", nameof(chatId));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
throw new ArgumentException("Template should not be empty", nameof(template));
|
||||
|
||||
return new TextMatch(userId, chatId, template, rule, createdDateTime);
|
||||
}
|
||||
|
||||
public bool IsMatches(long chatId, string text)
|
||||
{
|
||||
if (ChatId != chatId)
|
||||
return false;
|
||||
|
||||
switch (Rule)
|
||||
{
|
||||
case TextMatchRule.Full:
|
||||
return text.Contains(Template, StringComparison.OrdinalIgnoreCase);
|
||||
case TextMatchRule.AnyWord:
|
||||
var anyWords = Regex.Split(Template, @"\s+");
|
||||
return anyWords.Any(word => text.Contains(word, StringComparison.OrdinalIgnoreCase));
|
||||
case TextMatchRule.AllWords:
|
||||
var allWords = Regex.Split(Template, @"\s+");
|
||||
return allWords.All(word => text.Contains(word, StringComparison.OrdinalIgnoreCase));
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(Rule));
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/Nocr.TextMatcher.Async.Api.Contracts/IEvent.cs
Normal file
6
src/Nocr.TextMatcher.Async.Api.Contracts/IEvent.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Nocr.TextMatcher.Async.Api.Contracts;
|
||||
|
||||
public interface IEvent
|
||||
{
|
||||
public Guid Id { get; }
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Nocr.TextMatcher.Async.Api.Contracts;
|
||||
|
||||
public class TextMatchCreated : IEvent
|
||||
{
|
||||
public Guid Id => Guid.NewGuid();
|
||||
|
||||
public long ChatId { get; set; }
|
||||
}
|
||||
16
src/Nocr.TextMatcher.Async.Api.Contracts/TextMatchMatched.cs
Normal file
16
src/Nocr.TextMatcher.Async.Api.Contracts/TextMatchMatched.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Nocr.TextMatcher.Async.Api.Contracts;
|
||||
|
||||
public class TextMatchMatched : IEvent
|
||||
{
|
||||
public Guid Id => Guid.NewGuid();
|
||||
|
||||
public long UserId { get; set; }
|
||||
|
||||
public long ChatId { get; set; }
|
||||
|
||||
public string Text { get; set; }
|
||||
|
||||
public DateTimeOffset OccuredDateTime { get; set; }
|
||||
|
||||
public DateTimeOffset PublishedDateTime { get; set; }
|
||||
}
|
||||
12
src/Nocr.TextMatcher.Core/Options/RebusRabbitMqOptions.cs
Normal file
12
src/Nocr.TextMatcher.Core/Options/RebusRabbitMqOptions.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Nocr.TextMatcher.Core.Options;
|
||||
|
||||
public sealed class RebusRabbitMqOptions
|
||||
{
|
||||
public string ConnectionString { get; set; }
|
||||
|
||||
public string InputQueueName { get; set; }
|
||||
|
||||
public string DirectExchangeName { get; set; }
|
||||
|
||||
public string TopicsExchangeName { get; set; }
|
||||
}
|
||||
27
src/Nocr.TextMatcher.Host/Controllers/TextMatchController.cs
Normal file
27
src/Nocr.TextMatcher.Host/Controllers/TextMatchController.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Nocr.TextMatcher.Api.Contracts;
|
||||
using Nocr.TextMatcher.Api.Contracts.TextMatches;
|
||||
using Nocr.TextMatcher.Api.Contracts.TextMatches.Dto;
|
||||
using Nocr.TextMatcher.AppServices.TextMatches;
|
||||
using Nocr.TextMatcher.AppServices.TextMatches.Services;
|
||||
|
||||
namespace Nocr.TextMatcher.Host.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route(WebRoutes.TextMatches.Path)]
|
||||
public class TextMatchController : ControllerBase
|
||||
{
|
||||
private readonly ITextMatchService _textMatchService;
|
||||
|
||||
public TextMatchController(ITextMatchService textMatchService)
|
||||
{
|
||||
_textMatchService = textMatchService ?? throw new ArgumentNullException(nameof(textMatchService));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public Task<long> Create([FromBody] CreateTextMatchRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _textMatchService.Create(request.UserId, request.ChatId, request.Template, request.Rule,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@ public class HostBuilderFactory<TStartup> where TStartup : class
|
||||
if (!string.IsNullOrWhiteSpace(baseDirectory))
|
||||
configurationBuilder.SetBasePath(baseDirectory);
|
||||
|
||||
configurationBuilder.AddJsonFile("appsettings.protected.json", false);
|
||||
configurationBuilder.AddJsonFile("appsettings.protected.json", true);
|
||||
})
|
||||
.ConfigureWebHostDefaults(host => { host.UseStartup<TStartup>(); })
|
||||
.UseSerilog((ctx, logBuilder) =>
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using Nocr.TextMatcher.AppServices;
|
||||
using Nocr.TextMatcher.Core.Dates;
|
||||
using Nocr.TextMatcher.Core.Options;
|
||||
using Rebus.Bus;
|
||||
using Rebus.Config;
|
||||
using Rebus.Routing.TypeBased;
|
||||
using Rebus.Serialization.Json;
|
||||
|
||||
namespace Nocr.TextMatcher.Host.Infrastructure;
|
||||
|
||||
@ -16,10 +22,39 @@ public class Startup
|
||||
{
|
||||
services.AddSingleton<ICurrentDateProvider, DefaultCurrentDateProvider>();
|
||||
|
||||
services.AddControllers();
|
||||
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddSwaggerGen();
|
||||
|
||||
services.AddAppServices();
|
||||
|
||||
services.Configure<RebusRabbitMqOptions>(Configuration.GetSection(nameof(RebusRabbitMqOptions)));
|
||||
services.AddRebus((builder, ctx) =>
|
||||
builder.Transport(t =>
|
||||
{
|
||||
var rebusOptions = ctx.GetRequiredService<IOptions<RebusRabbitMqOptions>>().Value;
|
||||
t.UseRabbitMq(rebusOptions.ConnectionString, rebusOptions.InputQueueName)
|
||||
.DefaultQueueOptions(queue => queue.SetDurable(true))
|
||||
.ExchangeNames(rebusOptions.DirectExchangeName, rebusOptions.TopicsExchangeName);
|
||||
})
|
||||
.Serialization(s => s.UseSystemTextJson())
|
||||
.Logging(l => l.Serilog())
|
||||
.Routing(r => r.TypeBased()));
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (!env.IsProduction())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
app.UseEndpoints(builder => builder.MapControllers());
|
||||
|
||||
var bus = app.ApplicationServices.GetRequiredService<IBus>();
|
||||
bus.Advanced.Topics.Subscribe("nocr.telegram.listener").GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
@ -6,9 +6,18 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog.AspNetCore" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Label="Rebus">
|
||||
<PackageReference Include="Rebus.ServiceProvider" />
|
||||
<PackageReference Include="Rebus.RabbitMq" />
|
||||
<PackageReference Include="Rebus.Serilog" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Nocr.TextMatcher.Api.Contracts\Nocr.TextMatcher.Api.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Nocr.TextMatcher.AppServices\Nocr.TextMatcher.AppServices.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -8,5 +8,8 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RebusRabbitMqOptions": {
|
||||
"ConnectionString": "amqp://admin:admin@localhost:5672/"
|
||||
}
|
||||
}
|
||||
|
||||
28
src/Nocr.TextMatcher.Host/appsettings.DockerCompose.json
Normal file
28
src/Nocr.TextMatcher.Host/appsettings.DockerCompose.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information"
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"outputTemplate": "[{Level:u3}] {Timestamp:MM-dd HH:mm:ss} {TraceId} {SourceContext:l} {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "/var/log/nocr/text-matcher/nocr-text-matcher-.log",
|
||||
"outputTemplate": "[{Level:u3}] {Timestamp:dd-MM-yyyy HH:mm:ss} {TraceId} {SourceContext:l} {Message:lj}{NewLine}{Exception}",
|
||||
"fileSizeLimitBytes": 104857600,
|
||||
"rollingInterval": "Day",
|
||||
"rollOnFileSizeLimit": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RebusRabbitMqOptions": {
|
||||
"ConnectionString": "amqp://admin:admin@nocr-rabbitmq:5672/"
|
||||
}
|
||||
}
|
||||
@ -21,5 +21,8 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RebusRabbitMqOptions": {
|
||||
"ConnectionString": "amqp://admin:admin@nocr-rabbitmq:5672/"
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,14 @@
|
||||
"Override": {
|
||||
"Microsoft": "Information",
|
||||
"Microsoft.AspNetCore": "Error",
|
||||
"System.Net.Http.HttpClient": "Warning"
|
||||
"System.Net.Http.HttpClient": "Warning",
|
||||
"Rebus": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RebusRabbitMqOptions": {
|
||||
"InputQueueName": "nocr.text.matcher.queue",
|
||||
"DirectExchangeName": "nocr.direct",
|
||||
"TopicsExchangeName": "nocr.topics"
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Nocr.TextMatcher.AppServices\Nocr.TextMatcher.AppServices.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,68 @@
|
||||
using Nocr.TextMatcher.AppServices.Contracts.TextMatches;
|
||||
using Nocr.TextMatcher.AppServices.TextMatches;
|
||||
using Xunit;
|
||||
|
||||
namespace Nocr.TextMatcher.AppServices.UnitTests;
|
||||
|
||||
public class TextMatchTests
|
||||
{
|
||||
private const long UserId = 1;
|
||||
private const long ChatId = 1;
|
||||
|
||||
private static DateTimeOffset CreatedDateTime = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
[Fact]
|
||||
public void IsMatches_SameChatId_FullRule_MatchesText()
|
||||
{
|
||||
// Arrange
|
||||
var match = TextMatch.Initialize(UserId, ChatId, "велосипед", TextMatchRule.Full, CreatedDateTime);
|
||||
var text = "Продам снежный велосипед 100 лари. Гудаури.";
|
||||
|
||||
// Act
|
||||
var result = match.IsMatches(ChatId, text);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMatches_SameChatId_AnyWord_MatchesText()
|
||||
{
|
||||
// Arrange
|
||||
var match = TextMatch.Initialize(UserId, ChatId, "iphone айфон", TextMatchRule.AnyWord, CreatedDateTime);
|
||||
var text = "Продам айфон велосипед 100 лари. Гудаури.";
|
||||
|
||||
// Act
|
||||
var result = match.IsMatches(ChatId, text);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMatches_SameChatId_AllWords_MatchesText()
|
||||
{
|
||||
// Arrange
|
||||
var match = TextMatch.Initialize(UserId, ChatId, "iphone айфон", TextMatchRule.AnyWord, CreatedDateTime);
|
||||
var text = "Гомарджоба. Продам iphone (айфон) 1000 лари. Гудаури.";
|
||||
|
||||
// Act
|
||||
var result = match.IsMatches(ChatId, text);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsMatches_DifferentChatId_NotMatchesText()
|
||||
{
|
||||
// Arrange
|
||||
var match = TextMatch.Initialize(UserId, ChatId, "iphone", TextMatchRule.Full, CreatedDateTime);
|
||||
|
||||
// Act
|
||||
var result = match.IsMatches(ChatId, string.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user