Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Терзиогло Кирилл, Кононов Михаил, Хлопина Софья #49

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Tests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ public static void Main()
var testsToRun = new string[]
{
typeof(Task1_GetUserByIdTests).FullName,
//typeof(Task2_CreateUserTests).FullName,
//typeof(Task3_UpdateUserTests).FullName,
//typeof(Task4_PartiallyUpdateUserTests).FullName,
//typeof(Task5_DeleteUserTests).FullName,
//typeof(Task6_HeadUserByIdTests).FullName,
//typeof(Task7_GetUsersTests).FullName,
//typeof(Task8_GetUsersOptionsTests).FullName,
typeof(Task2_CreateUserTests).FullName,
typeof(Task3_UpdateUserTests).FullName,
typeof(Task4_PartiallyUpdateUserTests).FullName,
typeof(Task5_DeleteUserTests).FullName,
typeof(Task6_HeadUserByIdTests).FullName,
typeof(Task7_GetUsersTests).FullName,
typeof(Task8_GetUsersOptionsTests).FullName,
};
new AutoRun().Execute(new[]
{
Expand Down
2 changes: 1 addition & 1 deletion Tests/Task5_DeleteUserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public async Task Test4_Code404_WhenAlreadyCreatedAndDeleted()
lastName = "Condenado"
});

DeleteUser(createdUserId);
await DeleteUser(createdUserId);

var request = new HttpRequestMessage();
request.Method = HttpMethod.Delete;
Expand Down
4 changes: 2 additions & 2 deletions Tests/UsersApiTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ protected async Task<string> CreateUser(object user)
return createdUserId;
}

protected void DeleteUser(string userId)
protected async Task DeleteUser(string userId)
{
var request = new HttpRequestMessage();
request.Method = HttpMethod.Delete;
request.RequestUri = BuildUsersByIdUri(userId);
request.Headers.Add("Accept", "*/*");
var response = HttpClient.Send(request);
var response = await HttpClient.SendAsync(request);

response.StatusCode.Should().Be(HttpStatusCode.NoContent);
response.ShouldNotHaveHeader("Content-Type");
Expand Down
261 changes: 254 additions & 7 deletions WebApi.MinimalApi/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using Microsoft.AspNetCore.Mvc;
using AutoMapper;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Swashbuckle.AspNetCore.Annotations;
using WebApi.MinimalApi.Domain;
using WebApi.MinimalApi.Models;

Expand All @@ -8,20 +12,263 @@ namespace WebApi.MinimalApi.Controllers;
[ApiController]
public class UsersController : Controller
{
private readonly IUserRepository _userRepository;
private readonly IMapper _mapper;
// Чтобы ASP.NET положил что-то в userRepository требуется конфигурация
public UsersController(IUserRepository userRepository)
public UsersController(IUserRepository userRepository, IMapper mapper)
{
_userRepository = userRepository;
_mapper = mapper;
}

[HttpGet("{userId}")]

/// <summary>
/// Получить пользователя
/// </summary>
/// <param name="userId">Идентификатор пользователя</param>
/// <response code="200">OK</response>
/// <response code="404">Пользователь не найден</response>
[HttpGet("{userId}", Name = nameof(GetUserById))]
[HttpHead("{userId}")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(200, "OK", typeof(UserDto))]
[SwaggerResponse(404, "Пользователь не найден")]
public ActionResult<UserDto> GetUserById([FromRoute] Guid userId)
{
throw new NotImplementedException();
var user = _userRepository.FindById(userId);
if (user == null)
{
return NotFound();
}

if (HttpContext.Request.Method == "HEAD")
{
Response.ContentType = "application/json; charset=utf-8";
return Ok();
}

return Ok(_mapper.Map<UserDto>(user));
}

/// <summary>
/// Создать пользователя
/// </summary>
/// <remarks>
/// Пример запроса:
///
/// POST /api/users
/// {
/// "login": "johndoe375",
/// "firstName": "John",
/// "lastName": "Doe"
/// }
///
/// </remarks>
/// <param name="user">Данные для создания пользователя</param>
/// <response code="201">Пользователь создан</response>
/// <response code="400">Некорректные входные данные</response>
/// <response code="422">Ошибка при проверке</response>
[HttpPost]
public IActionResult CreateUser([FromBody] object user)
[Consumes("application/json")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(201, "Пользователь создан")]
[SwaggerResponse(400, "Некорректные входные данные")]
[SwaggerResponse(422, "Ошибка при проверке")]
public IActionResult CreateUser([FromBody] UserCreationDto? user)
{
if (user == null)
{
return BadRequest();
}

if (string.IsNullOrWhiteSpace(user.Login))
{
ModelState.AddModelError("Login", "Login is required");
}
else
{
if (user.Login.Any(c => !char.IsLetterOrDigit(c)))
{
ModelState.AddModelError("Login", "Login must consist only of letters and digits");
}
}

if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState);
}
var createdUserEntity = _mapper.Map<UserEntity>(user);
var tmp = _userRepository.Insert(createdUserEntity);
return CreatedAtRoute(
nameof(GetUserById),
new { userId = tmp.Id },
tmp.Id);
}

/// <summary>
/// Обновить пользователя
/// </summary>
/// <param name="userId">Идентификатор пользователя</param>
/// <param name="user">Обновленные данные пользователя</param>
/// <response code="201">Пользователь создан</response>
/// <response code="204">Пользователь обновлен</response>
/// <response code="400">Некорректные входные данные</response>
/// <response code="422">Ошибка при проверке</response>
[HttpPut("{userId}")]
[Consumes("application/json")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(201, "Пользователь создан")]
[SwaggerResponse(204, "Пользователь обновлен")]
[SwaggerResponse(400, "Некорректные входные данные")]
[SwaggerResponse(422, "Ошибка при проверке")]
public IActionResult UpdateUser([FromBody] UpdateDto? user, [FromRoute] Guid userId)
{
if (user == null || userId == Guid.Empty)
{
return BadRequest();
}

if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState);
}

var updatedUserEntity = new UserEntity(userId);
_mapper.Map(user, updatedUserEntity);
_userRepository.UpdateOrInsert(updatedUserEntity, out var isInserted);
if (!isInserted)
{
return NoContent();
}

return CreatedAtRoute(
nameof(GetUserById),
new { userId = updatedUserEntity.Id },
updatedUserEntity.Id);
}

/// <summary>
/// Частично обновить пользователя
/// </summary>
/// <param name="userId">Идентификатор пользователя</param>
/// <param name="patchDoc">JSON Patch для пользователя</param>
/// <response code="204">Пользователь обновлен</response>
/// <response code="400">Некорректные входные данные</response>
/// <response code="404">Пользователь не найден</response>
/// <response code="422">Ошибка при проверке</response>
[HttpPatch("{userId}")]
[Consumes("application/json-patch+json")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(204, "Пользователь обновлен")]
[SwaggerResponse(400, "Некорректные входные данные")]
[SwaggerResponse(404, "Пользователь не найден")]
[SwaggerResponse(422, "Ошибка при проверке")]
public IActionResult PartiallyUpdateUser([FromBody] JsonPatchDocument<UpdateDto>? patchDoc, [FromRoute] Guid userId)
{
if (patchDoc == null)
{
return BadRequest();
}

var user = _userRepository.FindById(userId);
if (user == null || userId == Guid.Empty)
{
return NotFound();
}

var updateDto = _mapper.Map<UpdateDto>(user);

patchDoc.ApplyTo(updateDto, ModelState);
TryValidateModel(updateDto);

if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState);
}

var updatedUserEntity = new UserEntity(userId);
_mapper.Map(updateDto, updatedUserEntity);
_userRepository.Update(updatedUserEntity);

return NoContent();
}

/// <summary>
/// Удалить пользователя
/// </summary>
/// <param name="userId">Идентификатор пользователя</param>
/// <response code="204">Пользователь обновлен</response>
/// <response code="404">Пользователь не найден</response>
[HttpDelete("{userId}")]
[Produces("application/json", "application/xml")]
[SwaggerResponse(204, "Пользователь удален")]
[SwaggerResponse(404, "Пользователь не найден")]
public IActionResult DeleteUser([FromRoute] Guid userId)
{
var user = _userRepository.FindById(userId);
if (user == null)
{
return NotFound();
}

_userRepository.Delete(userId);

return NoContent();
}

/// <summary>
/// Получить пользователей
/// </summary>
/// <param name="pageNumber">Номер страницы, по умолчанию 1</param>
/// <param name="pageSize">Размер страницы, по умолчанию 20</param>
/// <response code="200">OK</response>
[HttpGet(Name = nameof(GetUsers))]
[Produces("application/json", "application/xml")]
[ProducesResponseType(typeof(IEnumerable<UserDto>), 200)]
public IActionResult GetUsers([FromServices] LinkGenerator linkGenerator, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10)
{
pageNumber = (pageNumber < 1) ? 1 : pageNumber;
pageSize = (pageSize < 1) ? 1 : (pageSize > 20) ? 20 : pageSize;

var pageList = _userRepository.GetPage(pageNumber, pageSize);

var users = _mapper.Map<IEnumerable<UserDto>>(pageList);

var totalCount = pageList.TotalCount;
var totalPages = pageList.TotalPages;
var hasPreviousPage = pageNumber > 1;
var hasNextPage = pageNumber < totalPages;

var previousPageLink = hasPreviousPage
? linkGenerator.GetUriByRouteValues(HttpContext, null, new { pageNumber = pageNumber - 1, pageSize })
: null;

var nextPageLink = hasNextPage
? linkGenerator.GetUriByRouteValues(HttpContext, null, new { pageNumber = pageNumber + 1, pageSize })
: null;

var paginationHeader = new
{
previousPageLink,
nextPageLink,
totalCount,
pageSize,
currentPage = pageNumber,
totalPages
};
Response.Headers.Append("X-Pagination", JsonConvert.SerializeObject(paginationHeader));

return Ok(users);
}

/// <summary>
/// Опции по запросам о пользователях
/// </summary>
/// <response code="200">OK</response>
[HttpOptions]
[SwaggerResponse(200, "OK")]
public IActionResult GetUsersOptions()
{
throw new NotImplementedException();
Response.Headers.Append("Allow", "GET, POST, OPTIONS");
return Ok();
}
}
27 changes: 27 additions & 0 deletions WebApi.MinimalApi/Models/UserDto.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace WebApi.MinimalApi.Models;

public class UserDto
Expand All @@ -7,4 +10,28 @@ public class UserDto
public string FullName { get; set; }
public int GamesPlayed { get; set; }
public Guid? CurrentGameId { get; set; }
}

public class UserCreationDto
{
[Required]
public string Login { get; set; }

[DefaultValue("John")]
public string FirstName { get; set; }

[DefaultValue("Doe")]
public string LastName { get; set; }
}
public class UpdateDto
{
[Required]
[RegularExpression("^[0-9\\p{L}]*$", ErrorMessage = "Login should contain only letters or digits")]
public string Login { get; set; }

[Required]
public string FirstName { get; set; }

[Required]
public string LastName { get; set; }
}
Loading