Check in rewards NSD & payment

This commit is contained in:
LittleSheep 2025-05-15 22:03:51 +08:00
parent d7d4fde06a
commit 0db003abc2
16 changed files with 6968 additions and 53 deletions

View File

@ -318,10 +318,10 @@ public class AccountController(
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var userId = currentUser.Id;
var today = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
var localTime = new TimeOnly(0, 0);
var startOfDay = today.ToDateOnly().ToDateTime(localTime).ToUniversalTime().ToInstant();
var endOfDay = today.PlusDays(1).ToDateOnly().ToDateTime(localTime).ToUniversalTime().ToInstant();
var now = SystemClock.Instance.GetCurrentInstant();
var today = now.InUtc().Date;
var startOfDay = today.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
var endOfDay = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
var result = await db.AccountCheckInResults
.Where(x => x.AccountId == userId)

View File

@ -1,6 +1,7 @@
using System.Globalization;
using DysonNetwork.Sphere.Activity;
using DysonNetwork.Sphere.Connection;
using DysonNetwork.Sphere.Wallet;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Localization;
@ -13,6 +14,7 @@ public class AccountEventService(
ActivityService act,
WebSocketService ws,
IMemoryCache cache,
PaymentService payment,
IStringLocalizer<Localization.AccountEventResource> localizer
)
{
@ -163,12 +165,29 @@ public class AccountEventService(
{
Tips = tips,
Level = (CheckInResultLevel)Random.Next(Enum.GetValues<CheckInResultLevel>().Length),
AccountId = user.Id
AccountId = user.Id,
RewardExperience = 100,
RewardPoints = 10,
};
db.AccountCheckInResults.Add(result);
await db.SaveChangesAsync();
var now = SystemClock.Instance.GetCurrentInstant().InUtc().Date;
try
{
if (result.RewardPoints.HasValue)
await payment.CreateTransactionWithAccountAsync(
null,
user.Id,
WalletCurrency.SourcePoint,
result.RewardPoints.Value,
$"Check-in reward on {now:yyyy/MM/dd}"
);
}
catch
{
result.RewardPoints = null;
}
db.AccountCheckInResults.Add(result);
await act.CreateActivity(
user,
"accounts.check-in",

View File

@ -39,6 +39,8 @@ public class CheckInResult : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public CheckInResultLevel Level { get; set; }
public decimal? RewardPoints { get; set; }
public int? RewardExperience { get; set; }
[Column(TypeName = "jsonb")] public ICollection<FortuneTip> Tips { get; set; } = new List<FortuneTip>();
public Guid AccountId { get; set; }

View File

@ -74,6 +74,7 @@ public class AppDatabase(
public DbSet<Wallet.Transaction> PaymentTransactions { get; set; }
public DbSet<Developer.CustomApp> CustomApps { get; set; }
public DbSet<Developer.CustomAppSecret> CustomAppSecrets { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{

View File

@ -66,7 +66,7 @@ public class AuthController(
return challenge;
}
[HttpGet("challenge/{id}/factors")]
[HttpGet("challenge/{id:guid}/factors")]
public async Task<ActionResult<List<AccountAuthFactor>>> GetChallengeFactors([FromRoute] Guid id)
{
var challenge = await db.AuthChallenges
@ -78,7 +78,7 @@ public class AuthController(
: challenge.Account.AuthFactors.ToList();
}
[HttpPost("challenge/{id}/factors/{factorId:guid}")]
[HttpPost("challenge/{id:guid}/factors/{factorId:guid}")]
public async Task<ActionResult> RequestFactorCode([FromRoute] Guid id, [FromRoute] Guid factorId)
{
var challenge = await db.AuthChallenges
@ -97,11 +97,11 @@ public class AuthController(
public class PerformChallengeRequest
{
[Required] public long FactorId { get; set; }
[Required] public Guid FactorId { get; set; }
[Required] public string Password { get; set; } = string.Empty;
}
[HttpPatch("challenge/{id}")]
[HttpPatch("challenge/{id:guid}")]
public async Task<ActionResult<Challenge>> DoChallenge(
[FromRoute] Guid id,
[FromBody] PerformChallengeRequest request

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using NodaTime;
namespace DysonNetwork.Sphere.Developer;
@ -20,6 +21,19 @@ public class CustomApp : ModelBase
public Instant? VerifiedAt { get; set; }
[MaxLength(4096)] public string? VerifiedAs { get; set; }
[JsonIgnore] private ICollection<CustomAppSecret> Secrets { get; set; } = new List<CustomAppSecret>();
public Guid PublisherId { get; set; }
public Publisher.Publisher Developer { get; set; } = null!;
}
public class CustomAppSecret : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string Secret { get; set; } = null!;
[MaxLength(4096)] public string? Remarks { get; set; } = null!;
public Instant? ExpiredAt { get; set; }
public Guid AppId { get; set; }
public CustomApp App { get; set; } = null!;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class AddCustomAppsAndSecrets : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "issuer_app_id",
table: "payment_orders",
type: "uuid",
nullable: true);
migrationBuilder.CreateTable(
name: "custom_app_secrets",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false),
secret = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
remarks = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
app_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_custom_app_secrets", x => x.id);
table.ForeignKey(
name: "fk_custom_app_secrets_custom_apps_app_id",
column: x => x.app_id,
principalTable: "custom_apps",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_payment_orders_issuer_app_id",
table: "payment_orders",
column: "issuer_app_id");
migrationBuilder.CreateIndex(
name: "ix_custom_app_secrets_app_id",
table: "custom_app_secrets",
column: "app_id");
migrationBuilder.AddForeignKey(
name: "fk_payment_orders_custom_apps_issuer_app_id",
table: "payment_orders",
column: "issuer_app_id",
principalTable: "custom_apps",
principalColumn: "id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "fk_payment_orders_custom_apps_issuer_app_id",
table: "payment_orders");
migrationBuilder.DropTable(
name: "custom_app_secrets");
migrationBuilder.DropIndex(
name: "ix_payment_orders_issuer_app_id",
table: "payment_orders");
migrationBuilder.DropColumn(
name: "issuer_app_id",
table: "payment_orders");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class AddRewardToCheckIn : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "reward_experience",
table: "account_check_in_results",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "reward_points",
table: "account_check_in_results",
type: "numeric",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "reward_experience",
table: "account_check_in_results");
migrationBuilder.DropColumn(
name: "reward_points",
table: "account_check_in_results");
}
}
}

View File

@ -249,6 +249,14 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("integer")
.HasColumnName("level");
b.Property<int?>("RewardExperience")
.HasColumnType("integer")
.HasColumnName("reward_experience");
b.Property<decimal?>("RewardPoints")
.HasColumnType("numeric")
.HasColumnName("reward_points");
b.Property<ICollection<FortuneTip>>("Tips")
.IsRequired()
.HasColumnType("jsonb")
@ -1197,6 +1205,53 @@ namespace DysonNetwork.Sphere.Migrations
b.ToTable("custom_apps", (string)null);
});
modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<Guid>("AppId")
.HasColumnType("uuid")
.HasColumnName("app_id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<Instant?>("ExpiredAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expired_at");
b.Property<string>("Remarks")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("remarks");
b.Property<string>("Secret")
.IsRequired()
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("secret");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.HasKey("Id")
.HasName("pk_custom_app_secrets");
b.HasIndex("AppId")
.HasDatabaseName("ix_custom_app_secrets_app_id");
b.ToTable("custom_app_secrets", (string)null);
});
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroup", b =>
{
b.Property<Guid>("Id")
@ -2177,6 +2232,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("timestamp with time zone")
.HasColumnName("expired_at");
b.Property<Guid?>("IssuerAppId")
.HasColumnType("uuid")
.HasColumnName("issuer_app_id");
b.Property<Guid>("PayeeWalletId")
.HasColumnType("uuid")
.HasColumnName("payee_wallet_id");
@ -2201,6 +2260,9 @@ namespace DysonNetwork.Sphere.Migrations
b.HasKey("Id")
.HasName("pk_payment_orders");
b.HasIndex("IssuerAppId")
.HasDatabaseName("ix_payment_orders_issuer_app_id");
b.HasIndex("PayeeWalletId")
.HasDatabaseName("ix_payment_orders_payee_wallet_id");
@ -2742,6 +2804,18 @@ namespace DysonNetwork.Sphere.Migrations
b.Navigation("Developer");
});
modelBuilder.Entity("DysonNetwork.Sphere.Developer.CustomAppSecret", b =>
{
b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "App")
.WithMany()
.HasForeignKey("AppId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_custom_app_secrets_custom_apps_app_id");
b.Navigation("App");
});
modelBuilder.Entity("DysonNetwork.Sphere.Permission.PermissionGroupMember", b =>
{
b.HasOne("DysonNetwork.Sphere.Permission.PermissionGroup", "Group")
@ -3021,6 +3095,11 @@ namespace DysonNetwork.Sphere.Migrations
modelBuilder.Entity("DysonNetwork.Sphere.Wallet.Order", b =>
{
b.HasOne("DysonNetwork.Sphere.Developer.CustomApp", "IssuerApp")
.WithMany()
.HasForeignKey("IssuerAppId")
.HasConstraintName("fk_payment_orders_custom_apps_issuer_app_id");
b.HasOne("DysonNetwork.Sphere.Wallet.Wallet", "PayeeWallet")
.WithMany()
.HasForeignKey("PayeeWalletId")
@ -3033,6 +3112,8 @@ namespace DysonNetwork.Sphere.Migrations
.HasForeignKey("TransactionId")
.HasConstraintName("fk_payment_orders_payment_transactions_transaction_id");
b.Navigation("IssuerApp");
b.Navigation("PayeeWallet");
b.Navigation("Transaction");

View File

@ -1,8 +1,14 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Sphere.Developer;
using NodaTime;
namespace DysonNetwork.Sphere.Wallet;
public class WalletCurrency
{
public const string SourcePoint = "points";
}
public enum OrderStatus
{
Unpaid,
@ -25,6 +31,8 @@ public class Order : ModelBase
public Wallet PayeeWallet { get; set; } = null!;
public Guid? TransactionId { get; set; }
public Transaction? Transaction { get; set; }
public Guid? IssuerAppId { get; set; }
public CustomApp? IssuerApp { get; set; }
}
public enum TransactionType

View File

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using NodaTime;
namespace DysonNetwork.Sphere.Wallet;
@ -20,6 +21,36 @@ public class PaymentService(AppDatabase db, WalletService wat)
return order;
}
public async Task<Transaction> CreateTransactionWithAccountAsync(
Guid? payerAccountId,
Guid? payeeAccountId,
string currency,
decimal amount,
string? remarks = null,
TransactionType type = TransactionType.System
)
{
Wallet? payer = null, payee = null;
if (payerAccountId.HasValue)
payer = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payerAccountId.Value);
if (payeeAccountId.HasValue)
payee = await db.Wallets.FirstOrDefaultAsync(e => e.AccountId == payeeAccountId.Value);
if (payer == null && payerAccountId.HasValue)
throw new ArgumentException("Payer account was specified, but wallet was not found");
if (payee == null && payeeAccountId.HasValue)
throw new ArgumentException("Payee account was specified, but wallet was not found");
return await CreateTransactionAsync(
payer?.Id,
payee?.Id,
currency,
amount,
remarks,
type
);
}
public async Task<Transaction> CreateTransactionAsync(
Guid? payerWalletId,
Guid? payeeWalletId,
@ -29,6 +60,10 @@ public class PaymentService(AppDatabase db, WalletService wat)
TransactionType type = TransactionType.System
)
{
if (payerWalletId == null && payeeWalletId == null)
throw new ArgumentException("At least one wallet must be specified.");
if (amount <= 0) throw new ArgumentException("Cannot create transaction with negative or zero amount.");
var transaction = new Transaction
{
PayerWalletId = payerWalletId,
@ -41,26 +76,28 @@ public class PaymentService(AppDatabase db, WalletService wat)
if (payerWalletId.HasValue)
{
var payerPocket = await wat.GetOrCreateWalletPocketAsync(
(await db.Wallets.FindAsync(payerWalletId.Value))!.AccountId,
currency);
var (payerPocket, isNewlyCreated) =
await wat.GetOrCreateWalletPocketAsync(payerWalletId.Value, currency);
if (payerPocket.Amount < amount)
{
if (isNewlyCreated || payerPocket.Amount < amount)
throw new InvalidOperationException("Insufficient funds");
}
payerPocket.Amount -= amount;
await db.WalletPockets
.Where(p => p.Id == payerPocket.Id && p.Amount >= amount)
.ExecuteUpdateAsync(s =>
s.SetProperty(p => p.Amount, p => p.Amount - amount));
}
if (payeeWalletId.HasValue)
{
var payeeWallet = await db.Wallets.FindAsync(payeeWalletId.Value);
var payeePocket = await wat.GetOrCreateWalletPocketAsync(
payeeWallet!.AccountId,
currency);
var (payeePocket, isNewlyCreated) =
await wat.GetOrCreateWalletPocketAsync(payeeWalletId.Value, currency, amount);
payeePocket.Amount += amount;
if (!isNewlyCreated)
await db.WalletPockets
.Where(p => p.Id == payeePocket.Id)
.ExecuteUpdateAsync(s =>
s.SetProperty(p => p.Amount, p => p.Amount + amount));
}
db.PaymentTransactions.Add(transaction);
@ -159,7 +196,8 @@ public class PaymentService(AppDatabase db, WalletService wat)
return (order, refundTransaction);
}
public async Task<Transaction> TransferAsync(Guid payerAccountId, Guid payeeAccountId, string currency, decimal amount)
public async Task<Transaction> TransferAsync(Guid payerAccountId, Guid payeeAccountId, string currency,
decimal amount)
{
var payerWallet = await wat.GetWalletAsync(payerAccountId);
if (payerWallet == null)

View File

@ -0,0 +1,63 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Sphere.Wallet;
[ApiController]
[Route("/wallets")]
public class WalletController(AppDatabase db, WalletService ws) : ControllerBase
{
[HttpPost]
[Authorize]
public async Task<ActionResult<Wallet>> CreateWallet()
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
try
{
var wallet = await ws.CreateWalletAsync(currentUser.Id);
return Ok(wallet);
}
catch (Exception err)
{
return BadRequest(err.Message);
}
}
[HttpGet]
[Authorize]
public async Task<ActionResult<Wallet>> GetWallet()
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
var wallet = await ws.GetWalletAsync(currentUser.Id);
if (wallet is null) return NotFound("Wallet was not found, please create one first.");
return Ok(wallet);
}
[HttpGet("transactions")]
[Authorize]
public async Task<ActionResult<List<Transaction>>> GetTransactions(
[FromQuery] int offset = 0, [FromQuery] int take = 20
)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
var query = db.PaymentTransactions.AsQueryable()
.Include(t => t.PayeeWallet)
.Include(t => t.PayerWallet)
.Where(t => (t.PayeeWallet != null && t.PayeeWallet.AccountId == currentUser.Id) ||
(t.PayerWallet != null && t.PayerWallet.AccountId == currentUser.Id));
var transactionCount = await query.CountAsync();
var transactions = await query
.Skip(offset)
.Take(take)
.ToListAsync();
Response.Headers["X-Total"] = transactionCount.ToString();
return Ok(transactions);
}
}

View File

@ -13,10 +13,13 @@ public class WalletService(AppDatabase db)
public async Task<Wallet> CreateWalletAsync(Guid accountId)
{
var wallet = new Wallet
var existingWallet = await db.Wallets.FirstOrDefaultAsync(w => w.AccountId == accountId);
if (existingWallet != null)
{
AccountId = accountId
};
throw new InvalidOperationException($"Wallet already exists for account {accountId}");
}
var wallet = new Wallet { AccountId = accountId };
db.Wallets.Add(wallet);
await db.SaveChangesAsync();
@ -24,31 +27,23 @@ public class WalletService(AppDatabase db)
return wallet;
}
public async Task<WalletPocket> GetOrCreateWalletPocketAsync(Guid accountId, string currency)
public async Task<(WalletPocket wallet, bool isNewlyCreated)> GetOrCreateWalletPocketAsync(
Guid walletId,
string currency,
decimal? initialAmount = null
)
{
var wallet = await db.Wallets
.Include(w => w.Pockets)
.FirstOrDefaultAsync(w => w.AccountId == accountId);
if (wallet == null)
{
throw new InvalidOperationException($"Wallet not found for account {accountId}");
}
var pocket = wallet.Pockets.FirstOrDefault(p => p.Currency == currency);
if (pocket != null) return pocket;
var pocket = await db.WalletPockets.FirstOrDefaultAsync(p => p.Currency == currency && p.WalletId == walletId);
if (pocket != null) return (pocket, false);
pocket = new WalletPocket
{
Currency = currency,
Amount = 0,
WalletId = wallet.Id
Amount = initialAmount ?? 0,
WalletId = walletId
};
wallet.Pockets.Add(pocket);
await db.SaveChangesAsync();
return pocket;
db.WalletPockets.Add(pocket);
return (pocket, true);
}
}

View File

@ -43,6 +43,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFoundResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F28_003F290250f5_003FNotFoundResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFound_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff2c049af93e430aac427e8ff3cc9edd8763d5c9f006d7121ed1c5921585cba_003FNotFound_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANpgsqlEntityTypeBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fccb1faacaea4420db96b09857fc56178a1600_003Fd9_003F9acf9507_003FNpgsqlEntityTypeBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANullable_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F6a_003Fea17bf26_003FNullable_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOk_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01d30b32e2ff422cb80129ca2a441c4242600_003F3b_003F237bf104_003FOk_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsConfigurationServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6622dea924b14dc7aa3ee69d7c84e5735000_003Fe0_003F024ba0b7_003FOptionsConfigurationServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fd3_003F7b05b2bd_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
@ -56,6 +57,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc0e30e11d8f5456cb7a11b21ebee6c5a35c00_003F60_003F78b485f5_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASetPropertyCalls_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F458b5f22476b4599b87176214d5e4026c2327b148f4d3f885ee92362b4dac3_003FSetPropertyCalls_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASourceCustom_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F45_003F5839ca6c_003FSourceCustom_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_003ATagging_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F36f4c2e6baa65ba603de42eedad12ea36845aa35a910a6a82d82baf688e3e1_003FTagging_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003F12_003Fe0a28ad6_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>