Add one long hierarchical method, change some return codes
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: Get specific tag, specific post, specific comment
|
||||||
|
type: http
|
||||||
|
seq: 7
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseUrl}}/api/tags/high-res/posts/1/comments/1
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ public class AuthController(UserManager<User> userManager, ITokenService tokenSe
|
|||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
|
||||||
public async Task<ActionResult<SlimUserDto>> Register(RegisterDto dto)
|
public async Task<ActionResult<SlimUserDto>> Register(RegisterDto dto)
|
||||||
{
|
{
|
||||||
var user = new User
|
var user = new User
|
||||||
@@ -31,9 +32,18 @@ public class AuthController(UserManager<User> userManager, ITokenService tokenSe
|
|||||||
|
|
||||||
var result = await userManager.CreateAsync(user, dto.Password);
|
var result = await userManager.CreateAsync(user, dto.Password);
|
||||||
await userManager.AddToRoleAsync(user, UserRoles.Regular);
|
await userManager.AddToRoleAsync(user, UserRoles.Regular);
|
||||||
|
|
||||||
|
Dictionary<string, object> idk = new()
|
||||||
|
{
|
||||||
|
["errors"] = result.Errors
|
||||||
|
};
|
||||||
|
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
return BadRequest(result.Errors);
|
return Problem(
|
||||||
|
statusCode: StatusCodes.Status422UnprocessableEntity,
|
||||||
|
extensions: idk
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Ok(SlimUserDto.FromUser(user));
|
return Ok(SlimUserDto.FromUser(user));
|
||||||
}
|
}
|
||||||
@@ -46,6 +56,7 @@ public class AuthController(UserManager<User> userManager, ITokenService tokenSe
|
|||||||
/// <response code="401">If the credentials are incorrect</response>
|
/// <response code="401">If the credentials are incorrect</response>
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
public async Task<ActionResult<TokenDto>> Login(LoginDto dto)
|
public async Task<ActionResult<TokenDto>> Login(LoginDto dto)
|
||||||
{
|
{
|
||||||
@@ -66,6 +77,7 @@ public class AuthController(UserManager<User> userManager, ITokenService tokenSe
|
|||||||
/// <response code="401">If refresh token is missing or is expired</response>
|
/// <response code="401">If refresh token is missing or is expired</response>
|
||||||
[HttpPost("refresh")]
|
[HttpPost("refresh")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
public async Task<ActionResult<TokenDto>> Refresh(RefreshDto dto)
|
public async Task<ActionResult<TokenDto>> Refresh(RefreshDto dto)
|
||||||
{
|
{
|
||||||
@@ -88,6 +100,7 @@ public class AuthController(UserManager<User> userManager, ITokenService tokenSe
|
|||||||
/// <response code="401">If refresh token is missing or is expired</response>
|
/// <response code="401">If refresh token is missing or is expired</response>
|
||||||
[HttpPost("revoke")]
|
[HttpPost("revoke")]
|
||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
public async Task<ActionResult<TokenDto>> Revoke(RefreshDto dto)
|
public async Task<ActionResult<TokenDto>> Revoke(RefreshDto dto)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public class PostController(
|
|||||||
if (tag == null)
|
if (tag == null)
|
||||||
return Problem(
|
return Problem(
|
||||||
detail: $"'{tagName}' is not a valid tag",
|
detail: $"'{tagName}' is not a valid tag",
|
||||||
statusCode: StatusCodes.Status400BadRequest
|
statusCode: StatusCodes.Status422UnprocessableEntity
|
||||||
);
|
);
|
||||||
tags.Add(tag);
|
tags.Add(tag);
|
||||||
}
|
}
|
||||||
@@ -123,6 +123,7 @@ public class PostController(
|
|||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
|
||||||
public async Task<ActionResult<PostDto>> Create(CreatePostDto dto)
|
public async Task<ActionResult<PostDto>> Create(CreatePostDto dto)
|
||||||
{
|
{
|
||||||
var userId = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
|
var userId = HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
|
||||||
@@ -172,7 +173,9 @@ public class PostController(
|
|||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||||
[ProducesResponseType(StatusCodes.Status415UnsupportedMediaType)]
|
[ProducesResponseType(StatusCodes.Status415UnsupportedMediaType)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
|
||||||
public async Task<IActionResult> PatchFileContent(int postId, int fileId)
|
public async Task<IActionResult> PatchFileContent(int postId, int fileId)
|
||||||
{
|
{
|
||||||
var post = await postService.GetById(postId, includeUnfinished: true);
|
var post = await postService.GetById(postId, includeUnfinished: true);
|
||||||
@@ -186,7 +189,7 @@ public class PostController(
|
|||||||
if (userId != post.Author.Id) return Forbid();
|
if (userId != post.Author.Id) return Forbid();
|
||||||
|
|
||||||
if (fileRecord.FinishedDate != null)
|
if (fileRecord.FinishedDate != null)
|
||||||
return Problem(statusCode: StatusCodes.Status400BadRequest,
|
return Problem(statusCode: StatusCodes.Status409Conflict,
|
||||||
detail: "File was already uploaded.");
|
detail: "File was already uploaded.");
|
||||||
|
|
||||||
// Parse the Content-Range Header
|
// Parse the Content-Range Header
|
||||||
@@ -204,7 +207,7 @@ public class PostController(
|
|||||||
if (totalSizeFromHeader != totalSize)
|
if (totalSizeFromHeader != totalSize)
|
||||||
{
|
{
|
||||||
return Problem(
|
return Problem(
|
||||||
statusCode: StatusCodes.Status400BadRequest,
|
statusCode: StatusCodes.Status422UnprocessableEntity,
|
||||||
detail: $"Total file size mismatch. Expected: {totalSize} bytes, Received: {totalSizeFromHeader} bytes."
|
detail: $"Total file size mismatch. Expected: {totalSize} bytes, Received: {totalSizeFromHeader} bytes."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -383,6 +386,7 @@ public class PostController(
|
|||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
|
||||||
public async Task<ActionResult<PostDto>> Update(int id, EditPostDto dto)
|
public async Task<ActionResult<PostDto>> Update(int id, EditPostDto dto)
|
||||||
{
|
{
|
||||||
var post = await postService.GetById(id);
|
var post = await postService.GetById(id);
|
||||||
@@ -550,6 +554,7 @@ public class PostController(
|
|||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
|
||||||
public async Task<ActionResult<CommentDto>> Update(int postId, int commentId, EditCommentDto dto)
|
public async Task<ActionResult<CommentDto>> Update(int postId, int commentId, EditCommentDto dto)
|
||||||
{
|
{
|
||||||
var post = await postService.GetById(postId);
|
var post = await postService.GetById(postId);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using T120B165_ImgBoard.Dtos;
|
using T120B165_ImgBoard.Dtos.Comment;
|
||||||
using T120B165_ImgBoard.Dtos.Tag;
|
using T120B165_ImgBoard.Dtos.Tag;
|
||||||
using T120B165_ImgBoard.Models;
|
using T120B165_ImgBoard.Models;
|
||||||
using T120B165_ImgBoard.Services;
|
using T120B165_ImgBoard.Services;
|
||||||
@@ -10,7 +10,10 @@ namespace T120B165_ImgBoard.Controllers;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/tags")]
|
[Route("api/tags")]
|
||||||
public class TagController(ITagService tagService) : ControllerBase
|
public class TagController(
|
||||||
|
ITagService tagService,
|
||||||
|
IPostService postService,
|
||||||
|
ICommentService commentService) : ControllerBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new tag.
|
/// Creates a new tag.
|
||||||
@@ -126,4 +129,34 @@ public class TagController(ITagService tagService) : ControllerBase
|
|||||||
var updatedTag = await tagService.Update(tag, dto.Type);
|
var updatedTag = await tagService.Update(tag, dto.Type);
|
||||||
return Ok(updatedTag);
|
return Ok(updatedTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get specific tag, specific post comment.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tagName">Tag name</param>
|
||||||
|
/// <param name="postId">Post ID</param>
|
||||||
|
/// <param name="commentId">Comment ID</param>
|
||||||
|
/// <response code="200">Comment data</response>
|
||||||
|
/// <response code="400">If request is malformed</response>
|
||||||
|
/// <response code="404">If tag or post or comment is not found</response>
|
||||||
|
[HttpGet("{tagName}/posts/{postId:int}/comments/{commentId:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<CommentDto>> GetComment(string tagName, int postId, int commentId)
|
||||||
|
{
|
||||||
|
var tag = await tagService.GetByName(tagName);
|
||||||
|
if (tag == null) return NotFound();
|
||||||
|
|
||||||
|
var entry = await postService.GetById(postId);
|
||||||
|
if (entry == null) return NotFound();
|
||||||
|
|
||||||
|
if (entry.Tags.All(t => t.Name != tag.Name)) return NotFound();
|
||||||
|
|
||||||
|
var comment = await commentService.GetById(commentId);
|
||||||
|
if (comment == null || entry.Id != comment.OriginalPost.Id) return NotFound();
|
||||||
|
|
||||||
|
return Ok(CommentDto.FromComment(comment));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace T120B165_ImgBoard.Dtos.Comment;
|
namespace T120B165_ImgBoard.Dtos.Comment;
|
||||||
|
|
||||||
public record CreateCommentDto(string Text);
|
public record CreateCommentDto([Required] string Text);
|
||||||
public record EditCommentDto(string Text);
|
public record EditCommentDto([Required] string Text);
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace T120B165_ImgBoard.Dtos;
|
namespace T120B165_ImgBoard.Dtos;
|
||||||
|
|
||||||
public record LoginDto(string Email, string Password);
|
public record LoginDto([Required] string Email, [Required] string Password);
|
||||||
@@ -3,8 +3,11 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
namespace T120B165_ImgBoard.Dtos;
|
namespace T120B165_ImgBoard.Dtos;
|
||||||
|
|
||||||
public record RegisterDto(
|
public record RegisterDto(
|
||||||
|
[Required]
|
||||||
string UserName,
|
string UserName,
|
||||||
[EmailAddress]
|
[EmailAddress]
|
||||||
|
[Required]
|
||||||
string Email,
|
string Email,
|
||||||
|
[Required]
|
||||||
string Password
|
string Password
|
||||||
);
|
);
|
||||||
@@ -2,6 +2,8 @@ using System.Net;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
@@ -22,8 +24,34 @@ public class Program
|
|||||||
builder.Services.AddAuthorization();
|
builder.Services.AddAuthorization();
|
||||||
|
|
||||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||||
builder.Services.AddControllers().AddNewtonsoftJson(
|
builder.Services.AddControllers()
|
||||||
options => options.SerializerSettings.Converters.Add(new StringEnumConverter()));
|
.AddNewtonsoftJson(
|
||||||
|
options => options.SerializerSettings.Converters.Add(new StringEnumConverter()))
|
||||||
|
/*.ConfigureApiBehaviorOptions(opt =>
|
||||||
|
{
|
||||||
|
opt.InvalidModelStateResponseFactory = context =>
|
||||||
|
{
|
||||||
|
var problemDetails = new ValidationProblemDetails(context.ModelState);
|
||||||
|
|
||||||
|
var isBindingError = context.ModelState.Values.Any(v =>
|
||||||
|
v.ValidationState == ModelValidationState.Invalid && v.Errors.Any(e =>
|
||||||
|
e.Exception is not null || (!string.IsNullOrWhiteSpace(e.ErrorMessage) && e.ErrorMessage.Contains("body is required."))
|
||||||
|
));
|
||||||
|
|
||||||
|
if (isBindingError)
|
||||||
|
{
|
||||||
|
problemDetails.Status = StatusCodes.Status400BadRequest;
|
||||||
|
return new BadRequestObjectResult(problemDetails);
|
||||||
|
}
|
||||||
|
problemDetails.Status = StatusCodes.Status422UnprocessableEntity;
|
||||||
|
var result = new ObjectResult(problemDetails)
|
||||||
|
{
|
||||||
|
StatusCode = StatusCodes.Status422UnprocessableEntity,
|
||||||
|
};
|
||||||
|
result.ContentTypes.Add("application/json");
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})*/;
|
||||||
builder.Services.AddOpenApiDocument(cfg =>
|
builder.Services.AddOpenApiDocument(cfg =>
|
||||||
{
|
{
|
||||||
cfg.OperationProcessors.Add(new OperationSecurityScopeProcessor("auth"));
|
cfg.OperationProcessors.Add(new OperationSecurityScopeProcessor("auth"));
|
||||||
|
|||||||
Reference in New Issue
Block a user