From 5f99ea15e01d50c2059aa6aafcc9800c2e861e14 Mon Sep 17 00:00:00 2001 From: Ruberoid Date: Mon, 21 Jul 2025 16:38:22 +0300 Subject: [PATCH] Added new fixture user blocked by user. --- .../Users/Dto/UserData.cs | 5 + .../Users/IUsersController.cs | 6 ++ src/Nocr.Users.Api.Contracts/WebRoutes.cs | 4 + .../Users/Repositories/IUsersRepository.cs | 2 + .../Users/Services/IUsersService.cs | 4 + .../Users/Services/UsersService.cs | 32 +++++- src/Nocr.Users.AppServices/Users/User.cs | 12 +++ .../Controllers/UsersController.cs | 12 +++ ...50721124257_AddBotBlockedField.Designer.cs | 98 +++++++++++++++++++ .../20250721124257_AddBotBlockedField.cs | 29 ++++++ .../Migrations/UsersContextModelSnapshot.cs | 5 + .../Users/UserConfiguration.cs | 3 + .../Users/UsersRepository.cs | 7 ++ 13 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 src/Nocr.Users.Migrator/Migrations/20250721124257_AddBotBlockedField.Designer.cs create mode 100644 src/Nocr.Users.Migrator/Migrations/20250721124257_AddBotBlockedField.cs diff --git a/src/Nocr.Users.Api.Contracts/Users/Dto/UserData.cs b/src/Nocr.Users.Api.Contracts/Users/Dto/UserData.cs index 70b768b..e473193 100644 --- a/src/Nocr.Users.Api.Contracts/Users/Dto/UserData.cs +++ b/src/Nocr.Users.Api.Contracts/Users/Dto/UserData.cs @@ -9,6 +9,11 @@ public sealed class UserData /// public required string Username { get; set; } + /// + /// Заблокирован ли бот пользователем + /// + public bool BotBlocked { get; set; } + /// /// Список идентити пользователя /// diff --git a/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs b/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs index 86decf0..699d79d 100644 --- a/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs +++ b/src/Nocr.Users.Api.Contracts/Users/IUsersController.cs @@ -15,4 +15,10 @@ public interface IUsersController [Get(WebRoutes.Users.ByIdentity)] Task GetByIdentity([Query] UserIdentityType identityType, [Query] string identity, CancellationToken cancellationToken = default); + + [Post(WebRoutes.Users.BlockBot)] + Task BlockBot([Path] long id, CancellationToken cancellationToken = default); + + [Post(WebRoutes.Users.UnblockBot)] + Task UnblockBot([Path] long id, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Nocr.Users.Api.Contracts/WebRoutes.cs b/src/Nocr.Users.Api.Contracts/WebRoutes.cs index 4b3b969..e4c50f3 100644 --- a/src/Nocr.Users.Api.Contracts/WebRoutes.cs +++ b/src/Nocr.Users.Api.Contracts/WebRoutes.cs @@ -11,5 +11,9 @@ public static class WebRoutes public const string ById = "{id}"; public const string ByIdentity = "identity"; + + public const string BlockBot = "{id}/block-bot"; + + public const string UnblockBot = "{id}/unblock-bot"; } } \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/Repositories/IUsersRepository.cs b/src/Nocr.Users.AppServices/Users/Repositories/IUsersRepository.cs index e04c971..c4fc817 100644 --- a/src/Nocr.Users.AppServices/Users/Repositories/IUsersRepository.cs +++ b/src/Nocr.Users.AppServices/Users/Repositories/IUsersRepository.cs @@ -9,4 +9,6 @@ public interface IUsersRepository Task GetUserById(long id, CancellationToken cancellationToken = default); Task GetByIdentity(UserIdentityType identityType, string identity, CancellationToken cancellationToken = default); + + Task UpdateBotBlockedStatus(long userId, bool botBlocked, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/Services/IUsersService.cs b/src/Nocr.Users.AppServices/Users/Services/IUsersService.cs index 1ce5968..63eb529 100644 --- a/src/Nocr.Users.AppServices/Users/Services/IUsersService.cs +++ b/src/Nocr.Users.AppServices/Users/Services/IUsersService.cs @@ -11,4 +11,8 @@ public interface IUsersService Task GetById(long id, CancellationToken cancellationToken = default); Task GetByIdentity(UserIdentityType identityType, string identity, CancellationToken cancellationToken = default); + + Task BlockBot(long userId, CancellationToken cancellationToken = default); + + Task UnblockBot(long userId, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Nocr.Users.AppServices/Users/Services/UsersService.cs b/src/Nocr.Users.AppServices/Users/Services/UsersService.cs index 5852eb1..d85c7d3 100644 --- a/src/Nocr.Users.AppServices/Users/Services/UsersService.cs +++ b/src/Nocr.Users.AppServices/Users/Services/UsersService.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Logging; using Nocr.Users.Api.Contracts.Users; using Nocr.Users.Api.Contracts.Users.Dto; using Nocr.Users.AppServices.Users.Repositories; @@ -7,10 +8,12 @@ namespace Nocr.Users.AppServices.Users.Services; public sealed class UsersService : IUsersService { private readonly IUsersRepository _repository; + private readonly ILogger _logger; - public UsersService(IUsersRepository repository) + public UsersService(IUsersRepository repository, ILogger logger) { _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task Create(string username, string? telegramUsername, string? email, long? telegramId, @@ -48,6 +51,32 @@ public sealed class UsersService : IUsersService return MapToUserData(user); } + public async Task BlockBot(long userId, CancellationToken cancellationToken = default) + { + var user = await _repository.GetUserById(userId, cancellationToken); + if (user == null) + { + _logger.LogWarning("User with ID {UserId} not found for blocking bot", userId); + return; + } + + await _repository.UpdateBotBlockedStatus(userId, true, cancellationToken); + _logger.LogInformation("Bot blocked for user {UserId}", userId); + } + + public async Task UnblockBot(long userId, CancellationToken cancellationToken = default) + { + var user = await _repository.GetUserById(userId, cancellationToken); + if (user == null) + { + _logger.LogWarning("User with ID {UserId} not found for unblocking bot", userId); + return; + } + + await _repository.UpdateBotBlockedStatus(userId, false, cancellationToken); + _logger.LogInformation("Bot unblocked for user {UserId}", userId); + } + private static UserData? MapToUserData(User? user) { if (user == null) @@ -57,6 +86,7 @@ public sealed class UsersService : IUsersService { Id = user.Id, Username = user.Username, + BotBlocked = user.BotBlocked, Identities = user.Identities.Select(MapToUserIdentityData).ToArray() }; } diff --git a/src/Nocr.Users.AppServices/Users/User.cs b/src/Nocr.Users.AppServices/Users/User.cs index 278bc5b..ea12f11 100644 --- a/src/Nocr.Users.AppServices/Users/User.cs +++ b/src/Nocr.Users.AppServices/Users/User.cs @@ -5,6 +5,8 @@ public sealed class User public long Id { get; set; } public string Username { get; private set; } = default!; + + public bool BotBlocked { get; private set; } private readonly List _identities = new(); @@ -44,4 +46,14 @@ public sealed class User return new User(username, identities); } + + public void MarkAsBlocked() + { + BotBlocked = true; + } + + public void MarkAsUnblocked() + { + BotBlocked = false; + } } \ No newline at end of file diff --git a/src/Nocr.Users.Host/Controllers/UsersController.cs b/src/Nocr.Users.Host/Controllers/UsersController.cs index 9e6b486..71bd780 100644 --- a/src/Nocr.Users.Host/Controllers/UsersController.cs +++ b/src/Nocr.Users.Host/Controllers/UsersController.cs @@ -37,4 +37,16 @@ public class UsersController : ControllerBase { return _usersService.GetByIdentity(identityType, identity, cancellationToken); } + + [HttpPost(WebRoutes.Users.BlockBot)] + public Task BlockBot([FromRoute] long id, CancellationToken cancellationToken = default) + { + return _usersService.BlockBot(id, cancellationToken); + } + + [HttpPost(WebRoutes.Users.UnblockBot)] + public Task UnblockBot([FromRoute] long id, CancellationToken cancellationToken = default) + { + return _usersService.UnblockBot(id, cancellationToken); + } } \ No newline at end of file diff --git a/src/Nocr.Users.Migrator/Migrations/20250721124257_AddBotBlockedField.Designer.cs b/src/Nocr.Users.Migrator/Migrations/20250721124257_AddBotBlockedField.Designer.cs new file mode 100644 index 0000000..1ff567a --- /dev/null +++ b/src/Nocr.Users.Migrator/Migrations/20250721124257_AddBotBlockedField.Designer.cs @@ -0,0 +1,98 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Nocr.Users.Persistence; + +#nullable disable + +namespace Nocr.Users.Migrator.Migrations +{ + [DbContext(typeof(UsersContext))] + [Migration("20250721124257_AddBotBlockedField")] + partial class AddBotBlockedField + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Nocr.Users.AppServices.Users.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("BotBlocked") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + + b.Property("Username") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique() + .HasDatabaseName("UX_users_username"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Nocr.Users.AppServices.Users.UserIdentity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("Identity") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Identity", "Type") + .IsUnique() + .HasDatabaseName("UX_users_identity_identity_type"); + + b.ToTable("UserIdentity"); + }); + + modelBuilder.Entity("Nocr.Users.AppServices.Users.UserIdentity", b => + { + b.HasOne("Nocr.Users.AppServices.Users.User", null) + .WithMany("Identities") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Nocr.Users.AppServices.Users.User", b => + { + b.Navigation("Identities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Nocr.Users.Migrator/Migrations/20250721124257_AddBotBlockedField.cs b/src/Nocr.Users.Migrator/Migrations/20250721124257_AddBotBlockedField.cs new file mode 100644 index 0000000..5185eef --- /dev/null +++ b/src/Nocr.Users.Migrator/Migrations/20250721124257_AddBotBlockedField.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Nocr.Users.Migrator.Migrations +{ + /// + public partial class AddBotBlockedField : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BotBlocked", + table: "Users", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BotBlocked", + table: "Users"); + } + } +} diff --git a/src/Nocr.Users.Migrator/Migrations/UsersContextModelSnapshot.cs b/src/Nocr.Users.Migrator/Migrations/UsersContextModelSnapshot.cs index 5bd08d9..0d698b6 100644 --- a/src/Nocr.Users.Migrator/Migrations/UsersContextModelSnapshot.cs +++ b/src/Nocr.Users.Migrator/Migrations/UsersContextModelSnapshot.cs @@ -29,6 +29,11 @@ namespace Nocr.Users.Migrator.Migrations MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + b.Property("BotBlocked") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false); + b.Property("Username") .IsRequired() .HasColumnType("varchar(255)"); diff --git a/src/Nocr.Users.Persistence/Users/UserConfiguration.cs b/src/Nocr.Users.Persistence/Users/UserConfiguration.cs index 9eb9075..e981d12 100644 --- a/src/Nocr.Users.Persistence/Users/UserConfiguration.cs +++ b/src/Nocr.Users.Persistence/Users/UserConfiguration.cs @@ -11,6 +11,9 @@ public class UserConfiguration : IEntityTypeConfiguration builder.HasKey(x => x.Id); builder.Property(x => x.Username) .IsRequired(); + + builder.Property(x => x.BotBlocked) + .HasDefaultValue(false); builder.HasIndex(x => x.Username) .IsUnique() diff --git a/src/Nocr.Users.Persistence/Users/UsersRepository.cs b/src/Nocr.Users.Persistence/Users/UsersRepository.cs index 0c4f08a..9d1e833 100644 --- a/src/Nocr.Users.Persistence/Users/UsersRepository.cs +++ b/src/Nocr.Users.Persistence/Users/UsersRepository.cs @@ -35,4 +35,11 @@ public class UsersRepository : IUsersRepository .FirstOrDefaultAsync(x => x.Identities.Any(i => i.Type == identityType && i.Identity == identity), cancellationToken: cancellationToken); } + + public async Task UpdateBotBlockedStatus(long userId, bool botBlocked, CancellationToken cancellationToken = default) + { + await _db.Users + .Where(u => u.Id == userId) + .ExecuteUpdateAsync(u => u.SetProperty(x => x.BotBlocked, botBlocked), cancellationToken); + } } \ No newline at end of file