Skip to content
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
Binary file added 6,1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 6,2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 6,3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 6,4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 6,5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 6,6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions TaskHub/Api/Api.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand All @@ -12,7 +12,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
8 changes: 8 additions & 0 deletions TaskHub/Api/Controllers/Tasks/Request/CreateTaskRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Api.Controllers.Tasks.Request
{
public class CreateTaskRequest
{
public string? Title { get; set; }
public Guid UserId { get; set; }
}
}
7 changes: 7 additions & 0 deletions TaskHub/Api/Controllers/Tasks/Request/SetTaskTitleRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Api.Controllers.Tasks.Request
{
public class SetTaskTitleRequest
{
public string? Title { get; set; }
}
}
18 changes: 18 additions & 0 deletions TaskHub/Api/Controllers/Tasks/Response/TaskResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Api.Controllers.Tasks.Response
{
public class TaskResponse
{
public Guid Id { get; }
public string? Title { get; }
public Guid CreatedByUserId { get; }
public DateTimeOffset CreatedUtc { get; }

public TaskResponse(Guid id, string? title, Guid createdByUserId, DateTimeOffset createdUtc)
{
Id = id;
Title = title;
CreatedByUserId = createdByUserId;
CreatedUtc = createdUtc;
}
}
}
99 changes: 99 additions & 0 deletions TaskHub/Api/Controllers/Tasks/TasksController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using Api.Controllers.Tasks.Request;
using Api.Controllers.Tasks.Response;
using Api.Filters;
using Api.UseCases.Tasks.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace Api.Controllers.Tasks;

[Route("tasks")]
[StudentInfoHeadersFilter]
[RequestLoggingFilter]
public class TasksController : ControllerBase
{
private readonly IManageTaskUseCase _useCase;

public TasksController(IManageTaskUseCase useCase)
{
_useCase = useCase;
}

[HttpPost]
[ValidateCreateTaskRequestFilter]
public async Task<ActionResult<TaskResponse>> Create(CreateTaskRequest request, CancellationToken ct)
{
var result = await _useCase.CreateTaskAsync(request.Title, request.UserId, ct);
return Created("", result);
}

[HttpGet]
public async Task<ActionResult<IEnumerable<TaskResponse>>> GetAll(CancellationToken ct)
{
return Ok(await _useCase.GetAllTasksAsync(ct));
}

[HttpGet("{id}")]
[FromRouteTaskId]
public async Task<ActionResult<TaskResponse>> Get(
[FromRouteTaskId] string id,
CancellationToken ct)
{
var guidObj = HttpContext.Items["TaskId"];

if (guidObj == null)
{
return BadRequest("Идентификатор задачи не задан");
}

var guid = (Guid)guidObj;
var result = await _useCase.GetTaskByIdAsync(guid, ct);

if (result == null) return NotFound();
return Ok(result);
}

[HttpPut("{id}/title")]
[FromRouteTaskId]
[ValidateSetTaskTitleRequestFilter]
public async Task<IActionResult> SetTitle(
[FromRouteTaskId] string id,
SetTaskTitleRequest request,
CancellationToken ct)
{
var guidObj = HttpContext.Items["TaskId"];

if (guidObj == null)
{
return BadRequest("Идентификатор задачи не задан");
}

var guid = (Guid)guidObj;
await _useCase.SetTaskTitleAsync(guid, request.Title!, ct);
return NoContent();
}

[HttpDelete("{id}")]
[FromRouteTaskId]
public async Task<IActionResult> Delete(
[FromRouteTaskId] string id,
CancellationToken ct)
{
var guidObj = HttpContext.Items["TaskId"];

if (guidObj == null)
{
return BadRequest("Идентификатор задачи не задан");
}

var guid = (Guid)guidObj;
var deleted = await _useCase.DeleteTaskByIdAsync(guid, ct);
return deleted ? NoContent() : NotFound();
}

[HttpDelete]
public async Task<IActionResult> DeleteAll(CancellationToken ct)
{
await _useCase.DeleteAllTasksAsync(ct);
return NoContent();
}
}
5 changes: 5 additions & 0 deletions TaskHub/Api/Controllers/Users/UsersController.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using Api.Filters;
using Api.Controllers.Users.Request;
using Api.Controllers.Users.Response;
using Api.UseCases.Users.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace Api.Controllers.Users;

[ResponseTimeHeader]
[StudentInfoHeaders]

/// <summary>
/// Контроллер работы с пользователями
/// </summary>
Expand All @@ -29,6 +33,7 @@ public UsersController(IManageUserUseCase userUseCase)
/// <param name="cancellationToken">Токен отмены</param>
/// <returns>Созданный пользователь</returns>
[HttpPost]
[SetName]
public async Task<ActionResult<UserResponse>> CreateUserAsync(
[FromBody] CreateUserRequest? request,
CancellationToken cancellationToken)
Expand Down
35 changes: 35 additions & 0 deletions TaskHub/Api/Filters/FromRouteTaskIdAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Api.Filters;

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter)]
public class FromRouteTaskIdAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.RouteData.Values.TryGetValue("id", out var idObj))
{
context.Result = new BadRequestObjectResult("Идентификатор задачи не задан");
return;
}

string? idString = idObj?.ToString();

if (string.IsNullOrWhiteSpace(idString))
{
context.Result = new BadRequestObjectResult("Идентификатор задачи не задан");
return;
}

if (!Guid.TryParse(idString, out var guid))
{
context.Result = new BadRequestObjectResult("Идентификатор задачи имеет некорректный формат");
return;
}

context.HttpContext.Items["TaskId"] = guid;
}

public void OnActionExecuted(ActionExecutedContext context) { }
}
32 changes: 32 additions & 0 deletions TaskHub/Api/Filters/RequestLoggingFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace Api.Filters;

public class RequestLoggingFilter : Attribute, IActionFilter
{
private Stopwatch _stopwatch;
private ILogger _logger;

public void OnActionExecuting(ActionExecutingContext context)
{
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
_logger = loggerFactory.CreateLogger<RequestLoggingFilter>();

_stopwatch = Stopwatch.StartNew();
var httpMethod = context.HttpContext.Request.Method;
var path = context.HttpContext.Request.Path;

_logger.LogInformation("начало выполнения: {Method} {Path}", httpMethod, path);
}

public void OnActionExecuted(ActionExecutedContext context)
{
_stopwatch.Stop();
var statusCode = context.HttpContext.Response.StatusCode;
var elapsedMs = _stopwatch.ElapsedMilliseconds;

_logger.LogInformation("завершение: статус {StatusCode}, время {ElapsedMs} мс", statusCode, elapsedMs);
}
}
23 changes: 23 additions & 0 deletions TaskHub/Api/Filters/ResponseTimeHeaderAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;

namespace Api.Filters
{
public class ResponseTimeHeaderAttribute : ActionFilterAttribute
{
private Stopwatch _stopwatch;

public override void OnActionExecuting(ActionExecutingContext context)
{
_stopwatch = Stopwatch.StartNew();
}

public override void OnActionExecuted(ActionExecutedContext context)
{
_stopwatch.Stop();

context.HttpContext.Response.Headers["X-Response-Time-Ms"] =
_stopwatch.ElapsedMilliseconds.ToString();
}
}
}
6 changes: 6 additions & 0 deletions TaskHub/Api/Filters/SetNameAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Api.Filters
{
public class SetNameAttribute : ValidateUserRequestAttribute
{
}
}
15 changes: 15 additions & 0 deletions TaskHub/Api/Filters/StudentInfoHeadersAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc.Filters;

namespace Api.Filters
{
public class StudentInfoHeadersAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
var headers = context.HttpContext.Request.Headers;

headers["X-Student-Name"] = "Tumashova Marina";
headers["X-Student-Group"] = "RI-240912";
}
}
}
14 changes: 14 additions & 0 deletions TaskHub/Api/Filters/StudentInfoHeadersFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Mvc.Filters;

namespace Api.Filters;

public class StudentInfoHeadersFilter : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Response.Headers.Append("X-Student-Name", "Tumashova Marina");
context.HttpContext.Response.Headers.Append("X-Student-Group", "RI-240912");
}

public void OnActionExecuted(ActionExecutedContext context) { }
}
37 changes: 37 additions & 0 deletions TaskHub/Api/Filters/TaskIdModelBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Api.Filters;

public class TaskIdModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue("id");

if (value == ValueProviderResult.None)
{
bindingContext.ModelState.AddModelError("id", "Идентификатор задачи не задан");
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}

var idString = value.FirstValue;

if (string.IsNullOrWhiteSpace(idString))
{
bindingContext.ModelState.AddModelError("id", "Идентификатор задачи не задан");
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}

if (!Guid.TryParse(idString, out var guid))
{
bindingContext.ModelState.AddModelError("id", "Идентификатор задачи имеет некорректный формат");
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}

bindingContext.Result = ModelBindingResult.Success(guid);
return Task.CompletedTask;
}
}
44 changes: 44 additions & 0 deletions TaskHub/Api/Filters/ValidateCreateTaskRequestFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Api.Controllers.Tasks.Request;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Api.Filters;

public class ValidateCreateTaskRequestFilter : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ActionArguments.TryGetValue("request", out var requestObj))
{
context.Result = new BadRequestObjectResult("тело запроса отсутствует");
return;
}

if (requestObj == null)
{
context.Result = new BadRequestObjectResult("тело запроса отсутствует");
return;
}

var request = requestObj as CreateTaskRequest;
if (request == null)
{
context.Result = new BadRequestObjectResult("тело запроса отсутствует");
return;
}

if (request.UserId == Guid.Empty)
{
context.Result = new BadRequestObjectResult("идентификатор пользователя не задан");
return;
}

if (string.IsNullOrWhiteSpace(request.Title))
{
context.Result = new BadRequestObjectResult("название задачи не задано");
return;
}
}

public void OnActionExecuted(ActionExecutedContext context) { }
}
Loading