♻️ Replace normal streams with JetStream
🐛 Fix pass order didn't handled successfully
This commit is contained in:
@@ -14,8 +14,6 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="NATS.Client.Core" Version="2.6.8" />
|
|
||||||
<PackageReference Include="NATS.Client.JetStream" Version="2.6.8" />
|
|
||||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4"/>
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4"/>
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
|
||||||
|
@@ -22,8 +22,6 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Minio" Version="6.0.5" />
|
<PackageReference Include="Minio" Version="6.0.5" />
|
||||||
<PackageReference Include="NATS.Client.Core" Version="2.6.8" />
|
|
||||||
<PackageReference Include="NATS.Client.JetStream" Version="2.6.8" />
|
|
||||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@@ -3,6 +3,8 @@ using DysonNetwork.Drive.Storage;
|
|||||||
using DysonNetwork.Shared.Stream;
|
using DysonNetwork.Shared.Stream;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NATS.Client.Core;
|
using NATS.Client.Core;
|
||||||
|
using NATS.Client.JetStream.Models;
|
||||||
|
using NATS.Net;
|
||||||
|
|
||||||
namespace DysonNetwork.Drive.Startup;
|
namespace DysonNetwork.Drive.Startup;
|
||||||
|
|
||||||
@@ -14,12 +16,23 @@ public class BroadcastEventHandler(
|
|||||||
{
|
{
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
await foreach (var msg in nats.SubscribeAsync<byte[]>(AccountDeletedEvent.Type, cancellationToken: stoppingToken))
|
var js = nats.CreateJetStreamContext();
|
||||||
|
|
||||||
|
await js.EnsureStreamCreated("account_events", [AccountDeletedEvent.Type]);
|
||||||
|
|
||||||
|
var consumer = await js.CreateOrUpdateConsumerAsync("account_events",
|
||||||
|
new ConsumerConfig("drive_account_deleted_handler"), cancellationToken: stoppingToken);
|
||||||
|
|
||||||
|
await foreach (var msg in consumer.ConsumeAsync<byte[]>(cancellationToken: stoppingToken))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var evt = JsonSerializer.Deserialize<AccountDeletedEvent>(msg.Data);
|
var evt = JsonSerializer.Deserialize<AccountDeletedEvent>(msg.Data);
|
||||||
if (evt == null) continue;
|
if (evt == null)
|
||||||
|
{
|
||||||
|
await msg.AckAsync(cancellationToken: stoppingToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
logger.LogInformation("Account deleted: {AccountId}", evt.AccountId);
|
logger.LogInformation("Account deleted: {AccountId}", evt.AccountId);
|
||||||
|
|
||||||
@@ -46,11 +59,14 @@ public class BroadcastEventHandler(
|
|||||||
await transaction.RollbackAsync(cancellationToken: stoppingToken);
|
await transaction.RollbackAsync(cancellationToken: stoppingToken);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await msg.AckAsync(cancellationToken: stoppingToken);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error processing AccountDeleted");
|
logger.LogError(ex, "Error processing AccountDeleted");
|
||||||
|
await msg.NakAsync(cancellationToken: stoppingToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,8 +9,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="dotnet-etcd" Version="8.0.1" />
|
<PackageReference Include="dotnet-etcd" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
||||||
<PackageReference Include="NATS.Client.Core" Version="2.6.8" />
|
|
||||||
<PackageReference Include="NATS.Client.JetStream" Version="2.6.8" />
|
|
||||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@@ -13,6 +13,8 @@ using EFCore.BulkExtensions;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using NATS.Client.Core;
|
using NATS.Client.Core;
|
||||||
|
using NATS.Client.JetStream;
|
||||||
|
using NATS.Net;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using OtpNet;
|
using OtpNet;
|
||||||
using AuthService = DysonNetwork.Pass.Auth.AuthService;
|
using AuthService = DysonNetwork.Pass.Auth.AuthService;
|
||||||
@@ -189,7 +191,8 @@ public class AccountService(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Account> CreateBotAccount(Account account, Guid automatedId, string? pictureId, string? backgroundId)
|
public async Task<Account> CreateBotAccount(Account account, Guid automatedId, string? pictureId,
|
||||||
|
string? backgroundId)
|
||||||
{
|
{
|
||||||
var dupeAutomateCount = await db.Accounts.Where(a => a.AutomatedId == automatedId).CountAsync();
|
var dupeAutomateCount = await db.Accounts.Where(a => a.AutomatedId == automatedId).CountAsync();
|
||||||
if (dupeAutomateCount > 0)
|
if (dupeAutomateCount > 0)
|
||||||
@@ -230,7 +233,7 @@ public class AccountService(
|
|||||||
);
|
);
|
||||||
account.Profile.Background = CloudFileReferenceObject.FromProtoValue(file);
|
account.Profile.Background = CloudFileReferenceObject.FromProtoValue(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Accounts.Add(account);
|
db.Accounts.Add(account);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
@@ -442,7 +445,7 @@ public class AccountService(
|
|||||||
if (contact is null)
|
if (contact is null)
|
||||||
{
|
{
|
||||||
logger.LogWarning(
|
logger.LogWarning(
|
||||||
"Unable to send factor code to #{FactorId} with, due to no contact method was found...",
|
"Unable to send factor code to #{FactorId} with, due to no contact method was found...",
|
||||||
factor.Id
|
factor.Id
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -740,10 +743,14 @@ public class AccountService(
|
|||||||
db.Accounts.Remove(account);
|
db.Accounts.Remove(account);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
await nats.PublishAsync(AccountDeletedEvent.Type, JsonSerializer.SerializeToUtf8Bytes(new AccountDeletedEvent
|
var js = nats.CreateJetStreamContext();
|
||||||
{
|
await js.PublishAsync(
|
||||||
AccountId = account.Id,
|
AccountDeletedEvent.Type,
|
||||||
DeletedAt = SystemClock.Instance.GetCurrentInstant()
|
GrpcTypeHelper.ConvertObjectToByteString(new AccountDeletedEvent
|
||||||
}));
|
{
|
||||||
|
AccountId = account.Id,
|
||||||
|
DeletedAt = SystemClock.Instance.GetCurrentInstant()
|
||||||
|
}).ToByteArray()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -14,8 +14,6 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Nager.Holiday" Version="1.0.1" />
|
<PackageReference Include="Nager.Holiday" Version="1.0.1" />
|
||||||
<PackageReference Include="NATS.Client.Core" Version="2.6.8" />
|
|
||||||
<PackageReference Include="NATS.Client.JetStream" Version="2.6.8" />
|
|
||||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Pass.Wallet;
|
using DysonNetwork.Pass.Wallet;
|
||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
using DysonNetwork.Shared.Stream;
|
using DysonNetwork.Shared.Stream;
|
||||||
using NATS.Client.Core;
|
using NATS.Client.Core;
|
||||||
|
using NATS.Client.JetStream.Models;
|
||||||
|
using NATS.Net;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Startup;
|
namespace DysonNetwork.Pass.Startup;
|
||||||
|
|
||||||
@@ -13,18 +16,30 @@ public class BroadcastEventHandler(
|
|||||||
{
|
{
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
await foreach (var msg in nats.SubscribeAsync<byte[]>(PaymentOrderEventBase.Type, cancellationToken: stoppingToken))
|
var js = nats.CreateJetStreamContext();
|
||||||
|
|
||||||
|
await js.EnsureStreamCreated("payment_events", [PaymentOrderEventBase.Type]);
|
||||||
|
|
||||||
|
var consumer = await js.CreateOrUpdateConsumerAsync("payment_events",
|
||||||
|
new ConsumerConfig("pass_payment_handler"),
|
||||||
|
cancellationToken: stoppingToken);
|
||||||
|
|
||||||
|
await foreach (var msg in consumer.ConsumeAsync<byte[]>(cancellationToken: stoppingToken))
|
||||||
{
|
{
|
||||||
PaymentOrderEvent? evt = null;
|
PaymentOrderEvent? evt = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
evt = JsonSerializer.Deserialize<PaymentOrderEvent>(msg.Data);
|
evt = JsonSerializer.Deserialize<PaymentOrderEvent>(msg.Data, GrpcTypeHelper.SerializerOptions);
|
||||||
|
|
||||||
|
logger.LogInformation(
|
||||||
|
"Received order event: {ProductIdentifier} {OrderId}",
|
||||||
|
evt?.ProductIdentifier,
|
||||||
|
evt?.OrderId
|
||||||
|
);
|
||||||
|
|
||||||
if (evt?.ProductIdentifier is null ||
|
if (evt?.ProductIdentifier is null ||
|
||||||
!evt.ProductIdentifier.StartsWith(SubscriptionType.StellarProgram))
|
!evt.ProductIdentifier.StartsWith(SubscriptionType.StellarProgram))
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogInformation("Handling stellar program order: {OrderId}", evt.OrderId);
|
logger.LogInformation("Handling stellar program order: {OrderId}", evt.OrderId);
|
||||||
|
|
||||||
@@ -38,19 +53,20 @@ public class BroadcastEventHandler(
|
|||||||
);
|
);
|
||||||
if (order is null)
|
if (order is null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("Order with ID {OrderId} not found.", evt.OrderId);
|
logger.LogWarning("Order with ID {OrderId} not found. Redelivering.", evt.OrderId);
|
||||||
await nats.PublishAsync(PaymentOrderEventBase.Type, msg.Data, cancellationToken: stoppingToken);
|
await msg.NakAsync(cancellationToken: stoppingToken);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await subscriptions.HandleSubscriptionOrder(order);
|
await subscriptions.HandleSubscriptionOrder(order);
|
||||||
|
|
||||||
logger.LogInformation("Subscription for order {OrderId} handled successfully.", evt.OrderId);
|
logger.LogInformation("Subscription for order {OrderId} handled successfully.", evt.OrderId);
|
||||||
|
await msg.AckAsync(cancellationToken: stoppingToken);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error processing payment order event for order {OrderId}", evt?.OrderId);
|
logger.LogError(ex, "Error processing payment order event for order {OrderId}. Redelivering.", evt?.OrderId);
|
||||||
await nats.PublishAsync(PaymentOrderEventBase.Type, msg.Data, cancellationToken: stoppingToken);
|
await msg.NakAsync(cancellationToken: stoppingToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -207,9 +207,11 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddScoped<SafetyService>();
|
services.AddScoped<SafetyService>();
|
||||||
services.AddScoped<SocialCreditService>();
|
services.AddScoped<SocialCreditService>();
|
||||||
services.AddScoped<ExperienceService>();
|
services.AddScoped<ExperienceService>();
|
||||||
|
|
||||||
services.Configure<OidcProviderOptions>(configuration.GetSection("OidcProvider"));
|
services.Configure<OidcProviderOptions>(configuration.GetSection("OidcProvider"));
|
||||||
services.AddScoped<OidcProviderService>();
|
services.AddScoped<OidcProviderService>();
|
||||||
|
|
||||||
|
services.AddHostedService<BroadcastEventHandler>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,8 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using NATS.Client.Core;
|
using NATS.Client.Core;
|
||||||
|
using NATS.Client.JetStream;
|
||||||
|
using NATS.Net;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using AccountService = DysonNetwork.Pass.Account.AccountService;
|
using AccountService = DysonNetwork.Pass.Account.AccountService;
|
||||||
|
|
||||||
@@ -253,6 +255,27 @@ public class PaymentService(
|
|||||||
throw new InvalidOperationException("Order not found");
|
throw new InvalidOperationException("Order not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var js = nats.CreateJetStreamContext();
|
||||||
|
|
||||||
|
if (order.Status == OrderStatus.Paid)
|
||||||
|
{
|
||||||
|
await js.PublishAsync(
|
||||||
|
PaymentOrderEventBase.Type,
|
||||||
|
GrpcTypeHelper.ConvertObjectToByteString(new PaymentOrderEvent
|
||||||
|
{
|
||||||
|
OrderId = order.Id,
|
||||||
|
WalletId = payerWallet.Id,
|
||||||
|
AccountId = payerWallet.AccountId,
|
||||||
|
AppIdentifier = order.AppIdentifier,
|
||||||
|
ProductIdentifier = order.ProductIdentifier,
|
||||||
|
Meta = order.Meta ?? [],
|
||||||
|
Status = (int)order.Status,
|
||||||
|
}).ToByteArray()
|
||||||
|
);
|
||||||
|
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
if (order.Status != OrderStatus.Unpaid)
|
if (order.Status != OrderStatus.Unpaid)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Order is in invalid status: {order.Status}");
|
throw new InvalidOperationException($"Order is in invalid status: {order.Status}");
|
||||||
@@ -282,16 +305,19 @@ public class PaymentService(
|
|||||||
|
|
||||||
await NotifyOrderPaid(order, payerWallet, order.PayeeWallet);
|
await NotifyOrderPaid(order, payerWallet, order.PayeeWallet);
|
||||||
|
|
||||||
await nats.PublishAsync(PaymentOrderEventBase.Type, JsonSerializer.SerializeToUtf8Bytes(new PaymentOrderEvent
|
await js.PublishAsync(
|
||||||
{
|
PaymentOrderEventBase.Type,
|
||||||
OrderId = order.Id,
|
GrpcTypeHelper.ConvertObjectToByteString(new PaymentOrderEvent
|
||||||
WalletId = payerWallet.Id,
|
{
|
||||||
AccountId = payerWallet.AccountId,
|
OrderId = order.Id,
|
||||||
AppIdentifier = order.AppIdentifier,
|
WalletId = payerWallet.Id,
|
||||||
ProductIdentifier = order.ProductIdentifier,
|
AccountId = payerWallet.AccountId,
|
||||||
Meta = order.Meta ?? [],
|
AppIdentifier = order.AppIdentifier,
|
||||||
Status = (int)order.Status,
|
ProductIdentifier = order.ProductIdentifier,
|
||||||
}));
|
Meta = order.Meta ?? [],
|
||||||
|
Status = (int)order.Status,
|
||||||
|
}).ToByteArray()
|
||||||
|
);
|
||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
@@ -19,8 +19,6 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="NATS.Client.Core" Version="2.6.8" />
|
|
||||||
<PackageReference Include="NATS.Client.JetStream" Version="2.6.8" />
|
|
||||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@@ -3,8 +3,12 @@ using DysonNetwork.Pusher.Email;
|
|||||||
using DysonNetwork.Pusher.Notification;
|
using DysonNetwork.Pusher.Notification;
|
||||||
using DysonNetwork.Shared.Proto;
|
using DysonNetwork.Shared.Proto;
|
||||||
using DysonNetwork.Shared.Registry;
|
using DysonNetwork.Shared.Registry;
|
||||||
|
using DysonNetwork.Shared.Stream;
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
using NATS.Client.Core;
|
using NATS.Client.Core;
|
||||||
|
using NATS.Client.JetStream;
|
||||||
|
using NATS.Client.JetStream.Models;
|
||||||
|
using NATS.Net;
|
||||||
|
|
||||||
namespace DysonNetwork.Pusher.Services;
|
namespace DysonNetwork.Pusher.Services;
|
||||||
|
|
||||||
@@ -16,8 +20,8 @@ public class QueueBackgroundService(
|
|||||||
)
|
)
|
||||||
: BackgroundService
|
: BackgroundService
|
||||||
{
|
{
|
||||||
public const string QueueName = "pusher.queue";
|
public const string QueueName = "pusher_queue";
|
||||||
public const string QueueGroup = "pusher.workers";
|
private const string QueueGroup = "pusher_workers";
|
||||||
private readonly int _consumerCount = configuration.GetValue<int?>("ConsumerCount") ?? Environment.ProcessorCount;
|
private readonly int _consumerCount = configuration.GetValue<int?>("ConsumerCount") ?? Environment.ProcessorCount;
|
||||||
private readonly List<Task> _consumerTasks = [];
|
private readonly List<Task> _consumerTasks = [];
|
||||||
|
|
||||||
@@ -36,11 +40,16 @@ public class QueueBackgroundService(
|
|||||||
private async Task RunConsumerAsync(CancellationToken stoppingToken)
|
private async Task RunConsumerAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Queue consumer started");
|
logger.LogInformation("Queue consumer started");
|
||||||
|
var js = nats.CreateJetStreamContext();
|
||||||
|
|
||||||
await foreach (var msg in nats.SubscribeAsync<byte[]>(
|
await js.EnsureStreamCreated("pusher_events", [QueueName]);
|
||||||
QueueName,
|
|
||||||
queueGroup: QueueGroup,
|
var consumer = await js.CreateOrUpdateConsumerAsync(
|
||||||
cancellationToken: stoppingToken))
|
"pusher_events",
|
||||||
|
new ConsumerConfig(QueueGroup), // durable consumer
|
||||||
|
cancellationToken: stoppingToken);
|
||||||
|
|
||||||
|
await foreach (var msg in consumer.ConsumeAsync<byte[]>(cancellationToken: stoppingToken))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -48,10 +57,12 @@ public class QueueBackgroundService(
|
|||||||
if (message is not null)
|
if (message is not null)
|
||||||
{
|
{
|
||||||
await ProcessMessageAsync(msg, message, stoppingToken);
|
await ProcessMessageAsync(msg, message, stoppingToken);
|
||||||
|
await msg.AckAsync(cancellationToken: stoppingToken);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogWarning($"Invalid message format for {msg.Subject}");
|
logger.LogWarning($"Invalid message format for {msg.Subject}");
|
||||||
|
await msg.AckAsync(cancellationToken: stoppingToken); // Acknowledge invalid messages to avoid redelivery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
||||||
@@ -62,41 +73,31 @@ public class QueueBackgroundService(
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error in queue consumer");
|
logger.LogError(ex, "Error in queue consumer");
|
||||||
// Add a small delay to prevent tight error loops
|
await msg.NakAsync(cancellationToken: stoppingToken);
|
||||||
await Task.Delay(1000, stoppingToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask ProcessMessageAsync(NatsMsg<byte[]> rawMsg, QueueMessage message,
|
private async ValueTask ProcessMessageAsync(NatsJSMsg<byte[]> rawMsg, QueueMessage message,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using var scope = serviceProvider.CreateScope();
|
using var scope = serviceProvider.CreateScope();
|
||||||
|
|
||||||
logger.LogDebug("Processing message of type {MessageType}", message.Type);
|
logger.LogDebug("Processing message of type {MessageType}", message.Type);
|
||||||
|
|
||||||
try
|
switch (message.Type)
|
||||||
{
|
{
|
||||||
switch (message.Type)
|
case QueueMessageType.Email:
|
||||||
{
|
await ProcessEmailMessageAsync(message, scope);
|
||||||
case QueueMessageType.Email:
|
break;
|
||||||
await ProcessEmailMessageAsync(message, scope);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case QueueMessageType.PushNotification:
|
case QueueMessageType.PushNotification:
|
||||||
await ProcessPushNotificationMessageAsync(message, scope, cancellationToken);
|
await ProcessPushNotificationMessageAsync(message, scope, cancellationToken);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.LogWarning("Unknown message type: {MessageType}", message.Type);
|
logger.LogWarning("Unknown message type: {MessageType}", message.Type);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, "Error processing message of type {MessageType}", message.Type);
|
|
||||||
// Don't rethrow to prevent the message from being retried indefinitely
|
|
||||||
// In a production scenario, you might want to implement a dead-letter queue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,16 +127,8 @@ public class QueueBackgroundService(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
logger.LogDebug("Processing push notification for account {AccountId}", notification.AccountId);
|
||||||
{
|
await pushService.DeliverPushNotification(notification, cancellationToken);
|
||||||
logger.LogDebug("Processing push notification for account {AccountId}", notification.AccountId);
|
logger.LogDebug("Successfully processed push notification for account {AccountId}", notification.AccountId);
|
||||||
await pushService.DeliverPushNotification(notification, cancellationToken);
|
|
||||||
logger.LogDebug("Successfully processed push notification for account {AccountId}", notification.AccountId);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, "Error processing push notification for account {AccountId}", notification.AccountId);
|
|
||||||
// Don't rethrow to prevent the message from being retried indefinitely
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using DysonNetwork.Shared.Proto;
|
using DysonNetwork.Shared.Proto;
|
||||||
using NATS.Client.Core;
|
using NATS.Client.Core;
|
||||||
|
using NATS.Client.JetStream;
|
||||||
|
using NATS.Net;
|
||||||
|
|
||||||
namespace DysonNetwork.Pusher.Services;
|
namespace DysonNetwork.Pusher.Services;
|
||||||
|
|
||||||
@@ -20,7 +22,8 @@ public class QueueService(INatsConnection nats)
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
var rawMessage = GrpcTypeHelper.ConvertObjectToByteString(message).ToByteArray();
|
var rawMessage = GrpcTypeHelper.ConvertObjectToByteString(message).ToByteArray();
|
||||||
await nats.PublishAsync(QueueBackgroundService.QueueName, rawMessage);
|
var js = nats.CreateJetStreamContext();
|
||||||
|
await js.PublishAsync(QueueBackgroundService.QueueName, rawMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnqueuePushNotification(Notification.Notification notification, Guid userId, bool isSavable = false)
|
public async Task EnqueuePushNotification(Notification.Notification notification, Guid userId, bool isSavable = false)
|
||||||
@@ -35,7 +38,8 @@ public class QueueService(INatsConnection nats)
|
|||||||
Data = JsonSerializer.Serialize(notification)
|
Data = JsonSerializer.Serialize(notification)
|
||||||
};
|
};
|
||||||
var rawMessage = GrpcTypeHelper.ConvertObjectToByteString(message).ToByteArray();
|
var rawMessage = GrpcTypeHelper.ConvertObjectToByteString(message).ToByteArray();
|
||||||
await nats.PublishAsync(QueueBackgroundService.QueueName, rawMessage);
|
var js = nats.CreateJetStreamContext();
|
||||||
|
await js.PublishAsync(QueueBackgroundService.QueueName, rawMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,8 +21,7 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.3.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.3.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
|
||||||
<PackageReference Include="NATS.Client.Core" Version="2.6.8" />
|
<PackageReference Include="NATS.Net" Version="2.6.8" />
|
||||||
<PackageReference Include="NATS.Client.JetStream" Version="2.6.8" />
|
|
||||||
<PackageReference Include="NodaTime" Version="3.2.2" />
|
<PackageReference Include="NodaTime" Version="3.2.2" />
|
||||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
|
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
|
||||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
||||||
|
23
DysonNetwork.Shared/Stream/Streamer.cs
Normal file
23
DysonNetwork.Shared/Stream/Streamer.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using NATS.Client.JetStream;
|
||||||
|
using NATS.Client.JetStream.Models;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Shared.Stream;
|
||||||
|
|
||||||
|
public static class Streamer
|
||||||
|
{
|
||||||
|
public static async Task<INatsJSStream> EnsureStreamCreated(
|
||||||
|
this INatsJSContext context,
|
||||||
|
string stream,
|
||||||
|
ICollection<string>? subjects
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await context.CreateStreamAsync(new StreamConfig(stream, subjects ?? []));
|
||||||
|
}
|
||||||
|
catch (NatsJSException)
|
||||||
|
{
|
||||||
|
return await context.GetStreamAsync(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -30,8 +30,6 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="NATS.Client.Core" Version="2.6.8" />
|
|
||||||
<PackageReference Include="NATS.Client.JetStream" Version="2.6.8" />
|
|
||||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
using DysonNetwork.Shared.Stream;
|
using DysonNetwork.Shared.Stream;
|
||||||
using DysonNetwork.Sphere.Post;
|
using DysonNetwork.Sphere.Post;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NATS.Client.Core;
|
using NATS.Client.Core;
|
||||||
|
using NATS.Client.JetStream.Models;
|
||||||
|
using NATS.Net;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Startup;
|
namespace DysonNetwork.Sphere.Startup;
|
||||||
|
|
||||||
@@ -16,7 +19,7 @@ public class PaymentOrderAwardMeta
|
|||||||
{
|
{
|
||||||
[JsonPropertyName("account_id")] public Guid AccountId { get; set; }
|
[JsonPropertyName("account_id")] public Guid AccountId { get; set; }
|
||||||
[JsonPropertyName("post_id")] public Guid PostId { get; set; }
|
[JsonPropertyName("post_id")] public Guid PostId { get; set; }
|
||||||
[JsonPropertyName("amount")] public string Amount { get; set; }
|
[JsonPropertyName("amount")] public string Amount { get; set; } = null!;
|
||||||
[JsonPropertyName("attitude")] public PostReactionAttitude Attitude { get; set; }
|
[JsonPropertyName("attitude")] public PostReactionAttitude Attitude { get; set; }
|
||||||
[JsonPropertyName("message")] public string? Message { get; set; }
|
[JsonPropertyName("message")] public string? Message { get; set; }
|
||||||
}
|
}
|
||||||
@@ -29,14 +32,33 @@ public class BroadcastEventHandler(
|
|||||||
{
|
{
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
await foreach (var msg in nats.SubscribeAsync<byte[]>(PaymentOrderEventBase.Type, cancellationToken: stoppingToken))
|
var paymentTask = HandlePaymentOrders(stoppingToken);
|
||||||
|
var accountTask = HandleAccountDeletions(stoppingToken);
|
||||||
|
|
||||||
|
await Task.WhenAll(paymentTask, accountTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandlePaymentOrders(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
var js = nats.CreateJetStreamContext();
|
||||||
|
|
||||||
|
await js.EnsureStreamCreated("payment_events", [PaymentOrderEventBase.Type]);
|
||||||
|
|
||||||
|
var consumer = await js.CreateOrUpdateConsumerAsync("payment_events",
|
||||||
|
new ConsumerConfig("sphere_payment_handler"), cancellationToken: stoppingToken);
|
||||||
|
|
||||||
|
await foreach (var msg in consumer.ConsumeAsync<byte[]>(cancellationToken: stoppingToken))
|
||||||
{
|
{
|
||||||
PaymentOrderEvent? evt = null;
|
PaymentOrderEvent? evt = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
evt = JsonSerializer.Deserialize<PaymentOrderEvent>(msg.Data);
|
evt = JsonSerializer.Deserialize<PaymentOrderEvent>(msg.Data, GrpcTypeHelper.SerializerOptions);
|
||||||
|
|
||||||
// Every order goes into the MQ is already paid, so we skipped the status validation
|
logger.LogInformation(
|
||||||
|
"Received order event: {ProductIdentifier} {OrderId}",
|
||||||
|
evt?.ProductIdentifier,
|
||||||
|
evt?.OrderId
|
||||||
|
);
|
||||||
|
|
||||||
if (evt?.ProductIdentifier is null)
|
if (evt?.ProductIdentifier is null)
|
||||||
continue;
|
continue;
|
||||||
@@ -47,9 +69,9 @@ public class BroadcastEventHandler(
|
|||||||
{
|
{
|
||||||
var awardEvt = JsonSerializer.Deserialize<PaymentOrderAwardEvent>(msg.Data);
|
var awardEvt = JsonSerializer.Deserialize<PaymentOrderAwardEvent>(msg.Data);
|
||||||
if (awardEvt?.Meta == null) throw new ArgumentNullException(nameof(awardEvt));
|
if (awardEvt?.Meta == null) throw new ArgumentNullException(nameof(awardEvt));
|
||||||
|
|
||||||
var meta = awardEvt.Meta;
|
var meta = awardEvt.Meta;
|
||||||
|
|
||||||
logger.LogInformation("Handling post award order: {OrderId}", evt.OrderId);
|
logger.LogInformation("Handling post award order: {OrderId}", evt.OrderId);
|
||||||
|
|
||||||
await using var scope = serviceProvider.CreateAsyncScope();
|
await using var scope = serviceProvider.CreateAsyncScope();
|
||||||
@@ -60,28 +82,41 @@ public class BroadcastEventHandler(
|
|||||||
await ps.AwardPost(meta.PostId, meta.AccountId, amountNum, meta.Attitude, meta.Message);
|
await ps.AwardPost(meta.PostId, meta.AccountId, amountNum, meta.Attitude, meta.Message);
|
||||||
|
|
||||||
logger.LogInformation("Post award for order {OrderId} handled successfully.", evt.OrderId);
|
logger.LogInformation("Post award for order {OrderId} handled successfully.", evt.OrderId);
|
||||||
|
await msg.AckAsync(cancellationToken: stoppingToken);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
await nats.PublishAsync(PaymentOrderEventBase.Type, msg.Data, cancellationToken: stoppingToken);
|
await msg.NakAsync(cancellationToken: stoppingToken);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error processing payment order event for order {OrderId}", evt?.OrderId);
|
logger.LogError(ex, "Error processing payment order event for order {OrderId}", evt?.OrderId);
|
||||||
await nats.PublishAsync(PaymentOrderEventBase.Type, msg.Data, cancellationToken: stoppingToken);
|
await msg.NakAsync(cancellationToken: stoppingToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await foreach (var msg in nats.SubscribeAsync<byte[]>(AccountDeletedEvent.Type,
|
private async Task HandleAccountDeletions(CancellationToken stoppingToken)
|
||||||
cancellationToken: stoppingToken))
|
{
|
||||||
|
var js = nats.CreateJetStreamContext();
|
||||||
|
|
||||||
|
await js.EnsureStreamCreated("account_events", [AccountDeletedEvent.Type]);
|
||||||
|
|
||||||
|
var consumer = await js.CreateOrUpdateConsumerAsync("account_events",
|
||||||
|
new ConsumerConfig("sphere_account_deleted_handler"), cancellationToken: stoppingToken);
|
||||||
|
|
||||||
|
await foreach (var msg in consumer.ConsumeAsync<byte[]>(cancellationToken: stoppingToken))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var evt = JsonSerializer.Deserialize<AccountDeletedEvent>(msg.Data);
|
var evt = JsonSerializer.Deserialize<AccountDeletedEvent>(msg.Data, GrpcTypeHelper.SerializerOptions);
|
||||||
if (evt == null) continue;
|
if (evt == null)
|
||||||
|
{
|
||||||
|
await msg.AckAsync(cancellationToken: stoppingToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
logger.LogInformation("Account deleted: {AccountId}", evt.AccountId);
|
logger.LogInformation("Account deleted: {AccountId}", evt.AccountId);
|
||||||
|
|
||||||
@@ -120,11 +155,14 @@ public class BroadcastEventHandler(
|
|||||||
await transaction.RollbackAsync(cancellationToken: stoppingToken);
|
await transaction.RollbackAsync(cancellationToken: stoppingToken);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await msg.AckAsync(cancellationToken: stoppingToken);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "Error processing AccountDeleted");
|
logger.LogError(ex, "Error processing AccountDeleted");
|
||||||
|
await msg.NakAsync(cancellationToken: stoppingToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -138,6 +138,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3bef61b8a21d4c8e96872ecdd7782fa0e55000_003F7a_003F870020d0_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3bef61b8a21d4c8e96872ecdd7782fa0e55000_003F7a_003F870020d0_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fdf_003F3fcdc4d2_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStackFrameIterator_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fdf_003F3fcdc4d2_003FStackFrameIterator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStatusCodeResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F7c_003F8b7572ae_003FStatusCodeResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStatusCodeResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F7c_003F8b7572ae_003FStatusCodeResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStreamConfig_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F7140aa4493a2490fb306b1e68b5d533c98200_003Fbc_003Fccf922ff_003FStreamConfig_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStructuredTransformer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf3f51607a3e4e76b5b91640cd7409195c430_003F5c_003F73acd7b4_003FStructuredTransformer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AStructuredTransformer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf3f51607a3e4e76b5b91640cd7409195c430_003F5c_003F73acd7b4_003FStructuredTransformer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASyndicationFeed_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5b43b9cf654743f8b9a2eee23c625dd21dd30_003Fad_003Fd26b4d73_003FSyndicationFeed_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASyndicationFeed_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5b43b9cf654743f8b9a2eee23c625dd21dd30_003Fad_003Fd26b4d73_003FSyndicationFeed_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASyndicationItem_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5b43b9cf654743f8b9a2eee23c625dd21dd30_003Fe1_003Fb136d7be_003FSyndicationItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASyndicationItem_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5b43b9cf654743f8b9a2eee23c625dd21dd30_003Fe1_003Fb136d7be_003FSyndicationItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
Reference in New Issue
Block a user