mirror of
https://github.com/Govor-team/Govor.git
synced 2026-07-21 11:44:56 +00:00
Add username validation and initial friends feature
Introduced a UsernameValidator with Cyrillic and length checks, integrated into AuthService and registration flow. Added InvalidUsernameException and related interface. Updated User model to support friend requests, added FriendsController (stub), and configured Friendship entity in EF Core. Adjusted UserValidator max name length and removed navigation properties from PrivateChat.
This commit is contained in:
@@ -19,6 +19,7 @@ public class AuthServiceTests
|
||||
private Mock<IJwtService> _jwtServiceMock;
|
||||
private Mock<IUsersRepository> _usersRepositoryMock;
|
||||
private Mock<IAdminsRepository> _adminsRepositoryMock;
|
||||
private Mock<IUsernameValidator> _usernameValidatorMock;
|
||||
|
||||
private IAccountService _accountService;
|
||||
|
||||
@@ -38,12 +39,14 @@ public class AuthServiceTests
|
||||
_passwordHasherMock = new Mock<IPasswordHasher>();
|
||||
_jwtServiceMock = new Mock<IJwtService>();
|
||||
_adminsRepositoryMock = new Mock<IAdminsRepository>();
|
||||
_usernameValidatorMock = new Mock<IUsernameValidator>();
|
||||
|
||||
_accountService = new AuthService(
|
||||
_usersRepositoryMock.Object,
|
||||
_jwtServiceMock.Object,
|
||||
_passwordHasherMock.Object,
|
||||
_adminsRepositoryMock.Object
|
||||
_adminsRepositoryMock.Object,
|
||||
_usernameValidatorMock.Object
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using Govor.Application.Validators; // или другой namespace
|
||||
using Govor.Application.Exceptions.AuthService;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Services.Validators;
|
||||
|
||||
[TestFixture]
|
||||
public class UsernameValidatorTests
|
||||
{
|
||||
private UsernameValidator _validator;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_validator = new UsernameValidator();
|
||||
}
|
||||
|
||||
[TestCase("Иван")]
|
||||
[TestCase("Алексей")]
|
||||
[TestCase("Ёжик")]
|
||||
public void Validate_ValidUsernames_ShouldNotThrow(string username)
|
||||
{
|
||||
Assert.DoesNotThrow(() => _validator.Validate(username));
|
||||
}
|
||||
|
||||
[TestCase("Ivan")] // не кириллица
|
||||
[TestCase("123Иван")] // начинается не с буквы
|
||||
[TestCase("Иван123")] // содержит цифры
|
||||
[TestCase("!@#$")] // спецсимволы
|
||||
[TestCase("")] // пусто
|
||||
[TestCase("И")] // меньше минимума
|
||||
[TestCase("ИванИванИванИванИванИванИванИванИванИванИванИванИван")] // больше максимума (44 символа)
|
||||
public void Validate_InvalidUsernames_ShouldThrow(string username)
|
||||
{
|
||||
Assert.Throws<InvalidUsernameException>(() => _validator.Validate(username));
|
||||
}
|
||||
|
||||
[TestCase("Иван", ExpectedResult = true)]
|
||||
[TestCase("1234", ExpectedResult = false)]
|
||||
public bool TryValidate_ShouldReturnTrueRegardlessOfInput(string username)
|
||||
{
|
||||
return _validator.TryValidate(username);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,8 @@ public class AuthController : Controller
|
||||
|
||||
var invite = _invitesService.Validate(registrationRequest.InviteLink);
|
||||
|
||||
var token = await _accountService.RegistrationAsync(registrationRequest.Name, registrationRequest.Password, invite);
|
||||
var token = await _accountService.RegistrationAsync(registrationRequest.Name, registrationRequest.Password,
|
||||
invite);
|
||||
_logger.LogInformation($"Register request for {registrationRequest.Name}");
|
||||
return Ok(new { token });
|
||||
}
|
||||
@@ -49,6 +50,11 @@ public class AuthController : Controller
|
||||
_logger.LogWarning(ex, $"Invite link invalid: {registrationRequest.InviteLink}");
|
||||
return BadRequest("Invite link invalid.");
|
||||
}
|
||||
catch (InvalidUsernameException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"Invalid username: {registrationRequest.Name}");
|
||||
return BadRequest($"Invalid username: {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error during registration for user {Name}", registrationRequest.Name);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize(Roles = "User,Admin")]
|
||||
public class FriendsController : Controller
|
||||
{
|
||||
private readonly ILogger<FriendsController> _logger;
|
||||
|
||||
[HttpGet("search")]
|
||||
public async Task<IActionResult> Search([FromBody] string query)
|
||||
{
|
||||
return BadRequest("Not a valid request");
|
||||
}
|
||||
|
||||
[HttpPost("request")]
|
||||
public async Task<IActionResult> SendRequest([FromBody] Guid targetUserId)
|
||||
{
|
||||
return BadRequest("Not a valid request");
|
||||
}
|
||||
|
||||
[HttpGet("requests")]
|
||||
public async Task<IActionResult> GetIncomingRequests()
|
||||
{
|
||||
return BadRequest("Not a valid request");
|
||||
}
|
||||
|
||||
[HttpPost("accept")]
|
||||
public async Task<IActionResult> AcceptFriend([FromBody] Guid requesterId)
|
||||
{
|
||||
return BadRequest("Not a valid request");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetFriends()
|
||||
{
|
||||
return BadRequest("Not a valid request");
|
||||
}
|
||||
|
||||
private Guid GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = HttpContext.User?.FindFirst("userID")?.Value;
|
||||
_logger.LogInformation("Claims: {Claims}", string.Join(", ", HttpContext.User?.Claims.Select(c => $"{c.Type}: {c.Value}") ?? new string[0]));
|
||||
|
||||
if (string.IsNullOrEmpty(userIdClaim))
|
||||
{
|
||||
_logger.LogError("No userID claim found");
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return Guid.TryParse(userIdClaim, out var userId) ? userId : Guid.Empty;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.AdminsStuff;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Application.Services;
|
||||
using Govor.Application.Validators;
|
||||
using Govor.Core.Infrastructure.Extensions;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
using Govor.Core.Models;
|
||||
@@ -28,6 +29,7 @@ public static class ConfigurationProgramExtensions
|
||||
services.AddScoped<IUsersAdministration, UsersService>();
|
||||
services.AddScoped<IInvitesService, InvitesService>();
|
||||
services.AddScoped<IInvitationGenerator, InvitationGenerator>();
|
||||
services.AddScoped<IUsernameValidator, UsernameValidator>();
|
||||
|
||||
services.AddScoped<IStorageService>(sp =>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Govor.Core;
|
||||
|
||||
namespace Govor.Application.Exceptions.AuthService;
|
||||
|
||||
public class InvalidUsernameException(string message) : GovorCoreException(message)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
|
||||
namespace Govor.Application.Interfaces.Authentication;
|
||||
|
||||
public interface IUsernameValidator : IObjectValidator<string>
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Govor.API.Services.Authentication.Interfaces;
|
||||
using Govor.Application.Exceptions.AuthService;
|
||||
using Govor.Core.Infrastructure.Extensions;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
using Govor.Core.Repositories.Admins;
|
||||
|
||||
namespace Govor.Application.Services;
|
||||
@@ -14,21 +16,26 @@ public class AuthService : IAccountService
|
||||
private readonly IJwtService _jwtService;
|
||||
private readonly IUsersRepository _usersRepository;
|
||||
private readonly IAdminsRepository _adminsRepository;
|
||||
private readonly IUsernameValidator _usernameValidator;
|
||||
|
||||
public AuthService(IUsersRepository usersRepository,
|
||||
IJwtService jwtService,
|
||||
IPasswordHasher passwordHasher,
|
||||
IAdminsRepository adminsRepository
|
||||
IAdminsRepository adminsRepository,
|
||||
IUsernameValidator usernameValidator
|
||||
)
|
||||
{
|
||||
_usersRepository = usersRepository;
|
||||
_jwtService = jwtService;
|
||||
_passwordHasher = passwordHasher;
|
||||
_adminsRepository = adminsRepository;
|
||||
_usernameValidator = usernameValidator;
|
||||
}
|
||||
|
||||
public async Task<string> RegistrationAsync(string name, string password, Invitation invitation)
|
||||
{
|
||||
_usernameValidator.Validate(name);
|
||||
|
||||
if (await _usersRepository.ExistsUsernameAsync(name))
|
||||
throw new UserAlreadyExistException(name);
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using Govor.Application.Exceptions.AuthService;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
|
||||
namespace Govor.Application.Validators;
|
||||
|
||||
public class UsernameValidator : IUsernameValidator
|
||||
{
|
||||
private readonly Regex _usernameRegex = new(@"^[А-Яа-яЁё]+$", RegexOptions.Compiled);
|
||||
|
||||
public void Validate(string username)
|
||||
{
|
||||
if(username.Length < UserValidator.MIN_LENGHT_OF_NAME || username.Length > UserValidator.MAX_LENGHT_OF_NAME)
|
||||
throw new InvalidUsernameException($"Username must be between {UserValidator.MIN_LENGHT_OF_NAME} and {UserValidator.MAX_LENGHT_OF_NAME} characters.");
|
||||
|
||||
if (!_usernameRegex.IsMatch(username))
|
||||
throw new InvalidUsernameException("The username must be in Cyrillic and start with a letter.");
|
||||
}
|
||||
|
||||
public bool TryValidate(string username)
|
||||
{
|
||||
try
|
||||
{
|
||||
Validate(username);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Govor.Core.Infrastructure.Validators;
|
||||
public class UserValidator : IObjectValidator<User>
|
||||
{
|
||||
public const int MIN_LENGHT_OF_NAME = 4;
|
||||
public const int MAX_LENGHT_OF_NAME = 50;
|
||||
public const int MAX_LENGHT_OF_NAME = 44;
|
||||
|
||||
public void Validate(User user)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,5 @@ public class PrivateChat
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserAId { get; set; }
|
||||
public Guid UserBId { get; set; }
|
||||
public User UserA { get; set; }
|
||||
public User UserB { get; set; }
|
||||
public List<Message> Messages { get; set; } = new List<Message>();
|
||||
}
|
||||
@@ -13,4 +13,6 @@ public class User
|
||||
public DateTime WasOnline {get; set;}
|
||||
public Guid InviteId {get; set;}
|
||||
public Invitation? Invite { get; set; }
|
||||
public List<Friendship> SentFriendRequests { get; set; } = new();
|
||||
public List<Friendship> ReceivedFriendRequests { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Govor.Core.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Govor.Data.Configurations;
|
||||
|
||||
public class FriendshipConfiguration : IEntityTypeConfiguration<Friendship>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Friendship> builder)
|
||||
{
|
||||
builder.HasKey(f => f.Id);
|
||||
|
||||
builder.HasOne(f => f.Requester)
|
||||
.WithMany(u => u.SentFriendRequests)
|
||||
.HasForeignKey(f => f.RequesterId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(f => f.Addressee)
|
||||
.WithMany(u => u.ReceivedFriendRequests)
|
||||
.HasForeignKey(f => f.AddresseeId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.Property(f => f.Status)
|
||||
.IsRequired();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ public class GovorDbContext(DbContextOptions<GovorDbContext> options) : DbContex
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfiguration(new FriendshipConfiguration());
|
||||
modelBuilder.ApplyConfiguration(new UserConfiguration());
|
||||
modelBuilder.ApplyConfiguration(new InvitationConfiguration());
|
||||
modelBuilder.ApplyConfiguration(new AdminConfiguration());
|
||||
|
||||
Reference in New Issue
Block a user