Merge branch 'unstable' into 'main'
Swagger-docs Update! See merge request internship-2025/survey-webapp/survey-webapp!10
This commit is contained in:
commit
64a10ac32d
20 changed files with 287 additions and 18 deletions
|
|
@ -1,19 +1,31 @@
|
|||
using System.Security.Claims;
|
||||
using SurveyBackend.Core.Contexts;
|
||||
using SurveyBackend.Services.Exceptions;
|
||||
|
||||
namespace SurveyBackend.Contexts;
|
||||
|
||||
/// <summary>
|
||||
/// Упрощает получение UserId из JWT-токена
|
||||
/// </summary>
|
||||
public class UserContext : IUserContext
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Добавьте HttpContextAccessor в DI и будет счастье
|
||||
/// </summary>
|
||||
/// <param name="httpContextAccessor"></param>
|
||||
public UserContext(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает UserId из токена, при отсуствии кидает Unauthorized
|
||||
/// </summary>
|
||||
/// <exception cref="UnauthorizedAccessException"></exception>
|
||||
public int UserId =>
|
||||
int.Parse(
|
||||
_httpContextAccessor.HttpContext?.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)
|
||||
?.Value ?? throw new UnauthorizedAccessException());
|
||||
?.Value ?? throw new UnauthorizedException("Where's your token mister"));
|
||||
}
|
||||
|
|
@ -6,17 +6,32 @@ using IAuthorizationService = SurveyBackend.Core.Services.IAuthorizationService;
|
|||
|
||||
namespace SurveyBackend.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Контроллер для всего связанного с авторизацией пользователей
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
/// <summary>
|
||||
/// Нет ну вы прикалываетесь что ли мне ща каждый контроллер описывать?
|
||||
/// </summary>
|
||||
/// <param name="authorizationService"></param>
|
||||
public AuthController(IAuthorizationService authorizationService)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Авторизация
|
||||
/// </summary>
|
||||
/// <remarks>Принимает на вход email и password. При отсутствии такого email вернет 404, при неправильном пароле 401, при успехе 200 и валидный токен</remarks>
|
||||
/// <param name="loginData"></param>
|
||||
/// <response code="200">Success: Возвращает токен</response>
|
||||
/// <response code="401">Unauthorized: Неправильный пароль</response>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("login")]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
|
|
@ -27,6 +42,12 @@ public class AuthController : ControllerBase
|
|||
return Ok(new { token = token });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Регистрация
|
||||
/// </summary>
|
||||
/// <remarks>Принимает на вход кучу всяких полей, потом разберемся</remarks>
|
||||
/// <param name="registerData"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("register")]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
|
|
|
|||
|
|
@ -7,17 +7,25 @@ using SurveyLib.Core.Services;
|
|||
|
||||
namespace SurveyBackend.Controllers;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ApiController]
|
||||
[Route("api/surveys/{surveyId}/questions")]
|
||||
public class QuestionController : ControllerBase
|
||||
{
|
||||
private readonly IQuestionService _questionService;
|
||||
|
||||
/// <inheritdoc />
|
||||
public QuestionController(IQuestionService questionService, IUserContext userContext)
|
||||
{
|
||||
_questionService = questionService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает список вопросов из опроса по его ID
|
||||
/// </summary>
|
||||
/// <remarks>Получение вопросов по ID опроса. В случае отсутствия опроса с таким идентификатором выкидывает 404</remarks>
|
||||
/// <param name="surveyId"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
|
|
@ -29,6 +37,13 @@ public class QuestionController : ControllerBase
|
|||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить вопрос к опросу
|
||||
/// </summary>
|
||||
/// <remarks>К опросу с указанным ID добавляет вопрос. Если я правильно написал, при отсутствии такого опроса кинет 404</remarks>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="surveyId"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using SurveyLib.Core.Services;
|
|||
|
||||
namespace SurveyBackend.Controllers;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ApiController]
|
||||
[Route("api/surveys")]
|
||||
public class SurveyController : ControllerBase
|
||||
|
|
@ -17,12 +18,18 @@ public class SurveyController : ControllerBase
|
|||
private readonly ISurveyService _surveyService;
|
||||
private readonly IUserContext _userContext;
|
||||
|
||||
/// <inheritdoc />
|
||||
public SurveyController(ISurveyService surveyService, IUserContext userContext)
|
||||
{
|
||||
_surveyService = surveyService;
|
||||
_userContext = userContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить ВСЕ опросы
|
||||
/// </summary>
|
||||
/// <remarks>Возвращает массив вообще всех опросов</remarks>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(List<OutputSurveyDto>), StatusCodes.Status200OK)]
|
||||
|
|
@ -33,6 +40,12 @@ public class SurveyController : ControllerBase
|
|||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить опрос по ID
|
||||
/// </summary>
|
||||
/// <remarks>А что тут говорить то</remarks>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
|
|
@ -44,6 +57,12 @@ public class SurveyController : ControllerBase
|
|||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить новый опрос
|
||||
/// </summary>
|
||||
/// <remarks></remarks>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
|
|
@ -56,6 +75,12 @@ public class SurveyController : ControllerBase
|
|||
return Created();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Удалить опрос по ID
|
||||
/// </summary>
|
||||
/// <remarks>Опрос должен быть создан тобой чтоб его удалить</remarks>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[HttpDelete("{id}")]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
|
|
@ -67,6 +92,11 @@ public class SurveyController : ControllerBase
|
|||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получить МОИ опроса
|
||||
/// </summary>
|
||||
/// <remarks>Возвращает только опросы созданные нынешним юзером</remarks>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[HttpGet("my")]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ using SurveyLib.Core.Services;
|
|||
|
||||
namespace SurveyBackend.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Удалим когда-нибудь
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/test")]
|
||||
public class TestController : ControllerBase
|
||||
|
|
|
|||
|
|
@ -1,9 +1,21 @@
|
|||
namespace SurveyBackend.DTOs.Question;
|
||||
|
||||
/// <summary>
|
||||
/// Схема для создания нового Question
|
||||
/// </summary>
|
||||
public class CreateQuestionDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Название вопроса
|
||||
/// </summary>
|
||||
public required string Title { get; set; }
|
||||
/// <summary>
|
||||
/// Тип вопроса
|
||||
/// </summary>
|
||||
public required string QuestionType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Варианты ответа (только если вопрос с выбором)
|
||||
/// </summary>
|
||||
public List<string>? AnswerVariants { get; set; }
|
||||
}
|
||||
|
|
@ -1,8 +1,20 @@
|
|||
namespace SurveyBackend.DTOs.Question;
|
||||
|
||||
/// <summary>
|
||||
/// Выходная схема вариантов ответа
|
||||
/// </summary>
|
||||
public class OutputAnswerVariantDto
|
||||
{
|
||||
/// <summary>
|
||||
/// ID варианта ответа
|
||||
/// </summary>
|
||||
public required int Id { get; set; }
|
||||
/// <summary>
|
||||
/// ID родительского вопроса
|
||||
/// </summary>
|
||||
public required int QuestionId { get; set; }
|
||||
/// <summary>
|
||||
/// Текст варианта ответа
|
||||
/// </summary>
|
||||
public required string Text { get; set; }
|
||||
}
|
||||
|
|
@ -1,10 +1,32 @@
|
|||
namespace SurveyBackend.DTOs.Question;
|
||||
|
||||
/// <summary>
|
||||
/// Выходнпя схема вопроса
|
||||
/// </summary>
|
||||
public class OutputQuestionDto
|
||||
{
|
||||
/// <summary>
|
||||
/// ID вопроса
|
||||
/// </summary>
|
||||
public required int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID родительского опроса
|
||||
/// </summary>
|
||||
public required int SurveyId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Заголовок вопроса
|
||||
/// </summary>
|
||||
public required string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Тип вопроса
|
||||
/// </summary>
|
||||
public required string QuestionType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Варианты ответа
|
||||
/// </summary>
|
||||
public List<OutputAnswerVariantDto>? AnswerVariants { get; set; }
|
||||
}
|
||||
|
|
@ -1,7 +1,16 @@
|
|||
namespace SurveyBackend.DTOs.Survey;
|
||||
|
||||
/// <summary>
|
||||
/// Схема для создания нового опроса
|
||||
/// </summary>
|
||||
public class CreateSurveyDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Название опроса
|
||||
/// </summary>
|
||||
public required string Title { get; set; }
|
||||
/// <summary>
|
||||
/// Опциональное описание опроса
|
||||
/// </summary>
|
||||
public string Description { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -1,9 +1,24 @@
|
|||
namespace SurveyBackend.DTOs.Survey;
|
||||
|
||||
/// <summary>
|
||||
/// Выходная схема опроса
|
||||
/// </summary>
|
||||
public class OutputSurveyDto
|
||||
{
|
||||
/// <summary>
|
||||
/// ID опроса
|
||||
/// </summary>
|
||||
public required int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Название опроса
|
||||
/// </summary>
|
||||
public required string Title { get; set; }
|
||||
/// <summary>
|
||||
/// Описание опроса
|
||||
/// </summary>
|
||||
public required string Description { get; set; }
|
||||
/// <summary>
|
||||
/// Создатель опроса (опционально)
|
||||
/// </summary>
|
||||
public int? CreatedBy { get; set; }
|
||||
}
|
||||
|
|
@ -1,7 +1,16 @@
|
|||
namespace SurveyBackend.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Схема авторизации пользователя
|
||||
/// </summary>
|
||||
public record UserLoginDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Почта
|
||||
/// </summary>
|
||||
public required string Email { get; set; }
|
||||
/// <summary>
|
||||
/// Пароль
|
||||
/// </summary>
|
||||
public required string Password { get; set; }
|
||||
}
|
||||
|
|
@ -1,10 +1,32 @@
|
|||
namespace SurveyBackend.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Схема регистрации пользователя
|
||||
/// </summary>
|
||||
public record UserRegistrationDto
|
||||
{
|
||||
public string Email { get; set; }
|
||||
/// <summary>
|
||||
/// Почта
|
||||
/// </summary>
|
||||
public required string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Юзернейм
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Имя
|
||||
/// </summary>
|
||||
public string FirstName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Фамилия
|
||||
/// </summary>
|
||||
public string LastName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Пароль
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace SurveyBackend.Filters;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class EndpointAuthRequirementFilter : IOperationFilter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
if (!context.ApiDescription
|
||||
.ActionDescriptor
|
||||
.EndpointMetadata
|
||||
.OfType<AuthorizeAttribute>()
|
||||
.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
operation.Security = new List<OpenApiSecurityRequirement>
|
||||
{
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
[new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = JwtBearerDefaults.AuthenticationScheme
|
||||
}
|
||||
}] = new List<string>()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,16 @@ using SurveyBackend.DTOs;
|
|||
|
||||
namespace SurveyBackend.Mappers;
|
||||
|
||||
/// <summary>
|
||||
/// Маппер всего связанного с авторизацией
|
||||
/// </summary>
|
||||
public static class AuthMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Перегнать схему регистрации в нового юзера
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
public static User UserRegistrationToModel(UserRegistrationDto dto) => new User
|
||||
{
|
||||
Email = dto.Email,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,18 @@ using SurveyLib.Core.Models.QuestionVariants;
|
|||
|
||||
namespace SurveyBackend.Mappers;
|
||||
|
||||
/// <summary>
|
||||
/// Маппер всего про вопросы
|
||||
/// </summary>
|
||||
public static class QuestionMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Создание вопроса в модель
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="surveyId"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BadRequestException"></exception>
|
||||
public static QuestionBase QuestionCreationToModel(CreateQuestionDto dto, int surveyId)
|
||||
{
|
||||
return dto.QuestionType.ToLower() switch
|
||||
|
|
@ -32,6 +42,11 @@ public static class QuestionMapper
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Модель в выходную схему
|
||||
/// </summary>
|
||||
/// <param name="question"></param>
|
||||
/// <returns></returns>
|
||||
public static OutputQuestionDto ModelToQuestionDto(QuestionBase question)
|
||||
{
|
||||
var withAnswerVariants = question.GetType() != typeof(TextQuestion);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,17 @@ using SurveyLib.Core.Models;
|
|||
|
||||
namespace SurveyBackend.Mappers;
|
||||
|
||||
/// <summary>
|
||||
/// Маппер всего про опросы
|
||||
/// </summary>
|
||||
public static class SurveyMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Схема создания в модель
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public static Survey CreateDtoToModel(CreateSurveyDto dto, int userId)
|
||||
{
|
||||
return new Survey
|
||||
|
|
@ -15,6 +24,11 @@ public static class SurveyMapper
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Модель в выходную схему
|
||||
/// </summary>
|
||||
/// <param name="survey"></param>
|
||||
/// <returns></returns>
|
||||
public static OutputSurveyDto ModelToOutputDto(Survey survey)
|
||||
{
|
||||
return new OutputSurveyDto
|
||||
|
|
|
|||
|
|
@ -2,17 +2,29 @@ using SurveyBackend.Services.Exceptions;
|
|||
|
||||
namespace SurveyBackend.Middlewares;
|
||||
|
||||
/// <summary>
|
||||
/// Имбовая миддлваря, ловит все эксепшны, кастомные прокидывает как HTTP-exception, остальные кидает 502 и кайфуем
|
||||
/// </summary>
|
||||
public class ExceptionsMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<ExceptionsMiddleware> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Ну типа конструктор хз
|
||||
/// </summary>
|
||||
/// <param name="next"></param>
|
||||
/// <param name="logger"></param>
|
||||
public ExceptionsMiddleware(RequestDelegate next, ILogger<ExceptionsMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using SurveyBackend.Contexts;
|
|||
using SurveyBackend.Core.Contexts;
|
||||
using SurveyBackend.Core.Repositories;
|
||||
using SurveyBackend.Core.Services;
|
||||
using SurveyBackend.Filters;
|
||||
using SurveyBackend.Infrastructure.Data;
|
||||
using SurveyBackend.Infrastructure.Repositories;
|
||||
using SurveyBackend.Middlewares;
|
||||
|
|
@ -84,20 +85,11 @@ public class Program
|
|||
Name = "Authorization",
|
||||
Type = SecuritySchemeType.ApiKey
|
||||
});
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = JwtBearerDefaults.AuthenticationScheme
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
|
||||
c.OperationFilter<EndpointAuthRequirementFilter>();
|
||||
|
||||
var filePath = Path.Combine(System.AppContext.BaseDirectory, "SurveyBackend.API.xml");
|
||||
c.IncludeXmlComments(filePath);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@
|
|||
<RootNamespace>SurveyBackend</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DocumentationFile>bin\Debug\SurveyBackend.API.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\Release\SurveyBackend.API.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="appsettings.*.json">
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class SurveyService : ISurveyService
|
|||
public async Task UpdateSurveyAsync(Survey survey)
|
||||
{
|
||||
if (survey.CreatedBy != _userContext.UserId)
|
||||
throw new UnauthorizedAccessException("You are not authorized to update this survey.");
|
||||
throw new UnauthorizedException("You are not authorized to update this survey.");
|
||||
await _surveyRepository.UpdateAsync(survey);
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ public class SurveyService : ISurveyService
|
|||
|
||||
if (survey.CreatedBy != _userContext.UserId)
|
||||
{
|
||||
throw new UnauthorizedAccessException("You are not authorized to delete this survey.");
|
||||
throw new UnauthorizedException("You are not authorized to delete this survey.");
|
||||
}
|
||||
|
||||
await _surveyRepository.DeleteAsync(survey);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue