aboutsummaryrefslogtreecommitdiff
path: root/BackEnd/Timeline/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'BackEnd/Timeline/Controllers')
-rw-r--r--BackEnd/Timeline/Controllers/V2/TimelineBookmarkV2Controller.cs (renamed from BackEnd/Timeline/Controllers/TimelineBookmarkV2Controller.cs)9
-rw-r--r--BackEnd/Timeline/Controllers/V2/TimelinePostV2Controller.cs (renamed from BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs)29
-rw-r--r--BackEnd/Timeline/Controllers/V2/TimelineV2Controller.cs (renamed from BackEnd/Timeline/Controllers/TimelineV2Controller.cs)27
-rw-r--r--BackEnd/Timeline/Controllers/V2/UserV2Controller.cs185
-rw-r--r--BackEnd/Timeline/Controllers/V2/V2ControllerBase.cs54
5 files changed, 266 insertions, 38 deletions
diff --git a/BackEnd/Timeline/Controllers/TimelineBookmarkV2Controller.cs b/BackEnd/Timeline/Controllers/V2/TimelineBookmarkV2Controller.cs
index c2130b5a..a23a061b 100644
--- a/BackEnd/Timeline/Controllers/TimelineBookmarkV2Controller.cs
+++ b/BackEnd/Timeline/Controllers/V2/TimelineBookmarkV2Controller.cs
@@ -10,11 +10,11 @@ using Timeline.Services.Api;
using Timeline.Services.Timeline;
using Timeline.Services.User;
-namespace Timeline.Controllers
+namespace Timeline.Controllers.V2
{
[ApiController]
[Route("v2/users/{username}/bookmarks")]
- public class TimelineBookmarkV2Controller : MyControllerBase
+ public class TimelineBookmarkV2Controller : V2ControllerBase
{
private readonly IUserService _userService;
private readonly ITimelineService _timelineService;
@@ -32,7 +32,8 @@ namespace Timeline.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[HttpGet]
- public async Task<ActionResult<Page<TimelineBookmark>>> ListAsync([FromRoute][Username] string username, [FromQuery] int? page, [FromQuery] int? pageSize)
+ public async Task<ActionResult<Page<TimelineBookmark>>> ListAsync([FromRoute][Username] string username,
+ [FromQuery][PositiveInteger] int? page, [FromQuery][PositiveInteger] int? pageSize)
{
var userId = await _userService.GetUserIdByUsernameAsync(username);
if (!UserHasPermission(UserPermission.UserBookmarkManagement) && !await _timelineBookmarkService.CanReadBookmarksAsync(userId, GetOptionalAuthUserId()))
@@ -47,7 +48,7 @@ namespace Timeline.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[HttpGet("{index}")]
- public async Task<ActionResult<TimelineBookmark>> GetAsync([FromRoute][Username] string username, [FromRoute] int index)
+ public async Task<ActionResult<TimelineBookmark>> GetAsync([FromRoute][Username] string username, [FromRoute][PositiveInteger] int index)
{
var userId = await _userService.GetUserIdByUsernameAsync(username);
if (!UserHasPermission(UserPermission.UserBookmarkManagement) && !await _timelineBookmarkService.CanReadBookmarksAsync(userId, GetOptionalAuthUserId()))
diff --git a/BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs b/BackEnd/Timeline/Controllers/V2/TimelinePostV2Controller.cs
index 435ffece..4d486041 100644
--- a/BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs
+++ b/BackEnd/Timeline/Controllers/V2/TimelinePostV2Controller.cs
@@ -9,31 +9,27 @@ using Timeline.Helpers.Cache;
using Timeline.Models;
using Timeline.Models.Http;
using Timeline.Models.Validation;
-using Timeline.Services.Mapper;
using Timeline.Services.Timeline;
using Timeline.Services.User;
using Timeline.SignalRHub;
-namespace Timeline.Controllers
+namespace Timeline.Controllers.V2
{
[ApiController]
[Route("v2/timelines/{owner}/{timeline}/posts")]
- public class TimelinePostV2Controller : MyControllerBase
+ public class TimelinePostV2Controller : V2ControllerBase
{
private readonly ITimelineService _timelineService;
private readonly ITimelinePostService _postService;
- private readonly IGenericMapper _mapper;
-
private readonly MarkdownProcessor _markdownProcessor;
private readonly IHubContext<TimelineHub> _timelineHubContext;
- public TimelinePostV2Controller(ITimelineService timelineService, ITimelinePostService timelinePostService, IGenericMapper mapper, MarkdownProcessor markdownProcessor, IHubContext<TimelineHub> timelineHubContext)
+ public TimelinePostV2Controller(ITimelineService timelineService, ITimelinePostService timelinePostService, MarkdownProcessor markdownProcessor, IHubContext<TimelineHub> timelineHubContext)
{
_timelineService = timelineService;
_postService = timelinePostService;
- _mapper = mapper;
_markdownProcessor = markdownProcessor;
_timelineHubContext = timelineHubContext;
}
@@ -43,15 +39,16 @@ namespace Timeline.Controllers
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
- public async Task<ActionResult<Page<HttpTimelinePost>>> ListAsync([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery][Range(0, int.MaxValue)] int? page, [FromQuery][Range(1, int.MaxValue)] int? numberPerPage)
+ public async Task<ActionResult<Page<HttpTimelinePost>>> ListAsync([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline, [FromQuery] DateTime? modifiedSince,
+ [FromQuery][PositiveInteger] int? page, [FromQuery][PositiveInteger] int? pageSize)
{
var timelineId = await _timelineService.GetTimelineIdAsync(owner, timeline);
if (!UserHasPermission(UserPermission.AllTimelineManagement) && !await _timelineService.HasReadPermissionAsync(timelineId, GetOptionalAuthUserId()))
{
return Forbid();
}
- var postPage = await _postService.GetPostsV2Async(timelineId, modifiedSince, page, numberPerPage);
- var items = await _mapper.MapListAsync<HttpTimelinePost>(postPage.Items, Url, User);
+ var postPage = await _postService.GetPostsV2Async(timelineId, modifiedSince, page, pageSize);
+ var items = await MapListAsync<HttpTimelinePost>(postPage.Items);
return postPage.WithItems(items);
}
@@ -69,7 +66,7 @@ namespace Timeline.Controllers
return Forbid();
}
var post = await _postService.GetPostV2Async(timelineId, postId);
- var result = await _mapper.MapAsync<HttpTimelinePost>(post, Url, User);
+ var result = await MapAsync<HttpTimelinePost>(post);
return result;
}
@@ -143,7 +140,7 @@ namespace Timeline.Controllers
var data = body.DataList[i];
if (data is null)
- return UnprocessableEntity(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {i} is null."));
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidRequest, $"Data at index {i} is null."));
try
{
@@ -152,7 +149,7 @@ namespace Timeline.Controllers
}
catch (FormatException)
{
- return UnprocessableEntity(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {i} is not a valid base64 string."));
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidRequest, $"Data at index {i} is not a valid base64 string."));
}
}
@@ -163,12 +160,12 @@ namespace Timeline.Controllers
var group = TimelineHub.GenerateTimelinePostChangeListeningGroupName(timeline);
await _timelineHubContext.Clients.Group(group).SendAsync(nameof(ITimelineClient.OnTimelinePostChanged), timeline);
- var result = await _mapper.MapAsync<HttpTimelinePost>(post, Url, User);
+ var result = await MapAsync<HttpTimelinePost>(post);
return CreatedAtAction("Get", new { owner = owner, timeline = timeline, post = post.LocalId }, result);
}
catch (TimelinePostCreateDataException e)
{
- return UnprocessableEntity(new CommonResponse(ErrorCodes.Common.InvalidModel, $"Data at index {e.Index} is invalid. {e.Message}"));
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidRequest, $"Data at index {e.Index} is invalid. {e.Message}"));
}
}
@@ -188,7 +185,7 @@ namespace Timeline.Controllers
}
var entity = await _postService.PatchPostAsync(timelineId, post, new TimelinePostPatchRequest { Time = body.Time, Color = body.Color });
- var result = await _mapper.MapAsync<HttpTimelinePost>(entity, Url, User);
+ var result = await MapAsync<HttpTimelinePost>(entity);
return Ok(result);
}
diff --git a/BackEnd/Timeline/Controllers/TimelineV2Controller.cs b/BackEnd/Timeline/Controllers/V2/TimelineV2Controller.cs
index 9811cbed..7bc02dc2 100644
--- a/BackEnd/Timeline/Controllers/TimelineV2Controller.cs
+++ b/BackEnd/Timeline/Controllers/V2/TimelineV2Controller.cs
@@ -2,42 +2,33 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Timeline.Entities;
using Timeline.Models.Http;
using Timeline.Models.Validation;
using Timeline.Services;
-using Timeline.Services.Mapper;
using Timeline.Services.Timeline;
using Timeline.Services.User;
-namespace Timeline.Controllers
+namespace Timeline.Controllers.V2
{
[ApiController]
[Route("v2/timelines")]
- public class TimelineV2Controller : MyControllerBase
+ public class TimelineV2Controller : V2ControllerBase
{
private ITimelineService _timelineService;
- private IGenericMapper _mapper;
private IUserService _userService;
- public TimelineV2Controller(ITimelineService timelineService, IGenericMapper mapper, IUserService userService)
+ public TimelineV2Controller(ITimelineService timelineService, IUserService userService)
{
_timelineService = timelineService;
- _mapper = mapper;
_userService = userService;
}
- private Task<HttpTimeline> MapAsync(TimelineEntity entity)
- {
- return _mapper.MapAsync<HttpTimeline>(entity, Url, User);
- }
-
[HttpGet("{owner}/{timeline}")]
public async Task<ActionResult<HttpTimeline>> GetAsync([FromRoute][Username] string owner, [FromRoute][TimelineName] string timeline)
{
var timelineId = await _timelineService.GetTimelineIdAsync(owner, timeline);
var t = await _timelineService.GetTimelineAsync(timelineId);
- return await MapAsync(t);
+ return await MapAsync<HttpTimeline>(t);
}
[HttpPatch("{owner}/{timeline}")]
@@ -54,9 +45,9 @@ namespace Timeline.Controllers
{
return Forbid();
}
- await _timelineService.ChangePropertyAsync(timelineId, _mapper.AutoMapperMap<TimelineChangePropertyParams>(body));
+ await _timelineService.ChangePropertyAsync(timelineId, AutoMapperMap<TimelineChangePropertyParams>(body));
var t = await _timelineService.GetTimelineAsync(timelineId);
- return await MapAsync(t);
+ return await MapAsync<HttpTimeline>(t);
}
[HttpDelete("{owner}/{timeline}")]
@@ -99,7 +90,7 @@ namespace Timeline.Controllers
}
catch (EntityNotExistException e) when (e.EntityType.Equals(EntityTypes.User))
{
- return UnprocessableEntity(new CommonResponse(ErrorCodes.Common.InvalidModel, "Member username does not exist."));
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidRequest, "Member username does not exist."));
}
await _timelineService.AddMemberAsync(timelineId, userId);
return NoContent();
@@ -127,7 +118,7 @@ namespace Timeline.Controllers
}
catch (EntityNotExistException e) when (e.EntityType.Equals(EntityTypes.User))
{
- return UnprocessableEntity(new CommonResponse(ErrorCodes.Common.InvalidModel, "Member username does not exist."));
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidRequest, "Member username does not exist."));
}
await _timelineService.RemoveMemberAsync(timelineId, userId);
return NoContent();
@@ -144,7 +135,7 @@ namespace Timeline.Controllers
var authUserId = GetAuthUserId();
var authUser = await _userService.GetUserAsync(authUserId);
var timeline = await _timelineService.CreateTimelineAsync(authUserId, body.Name);
- var result = await MapAsync(timeline);
+ var result = await MapAsync<HttpTimeline>(timeline);
return CreatedAtAction("Get", new { owner = authUser.Username, timeline = body.Name }, result);
}
}
diff --git a/BackEnd/Timeline/Controllers/V2/UserV2Controller.cs b/BackEnd/Timeline/Controllers/V2/UserV2Controller.cs
new file mode 100644
index 00000000..40657ad1
--- /dev/null
+++ b/BackEnd/Timeline/Controllers/V2/UserV2Controller.cs
@@ -0,0 +1,185 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using Timeline.Auth;
+using Timeline.Models;
+using Timeline.Models.Http;
+using Timeline.Models.Validation;
+using Timeline.Services;
+using Timeline.Services.User;
+
+namespace Timeline.Controllers.V2
+{
+ /// <summary>
+ /// Operations about users.
+ /// </summary>
+ [ApiController]
+ [Route("v2/users")]
+ public class UserV2Controller : V2ControllerBase
+ {
+ private readonly IUserService _userService;
+ private readonly IUserPermissionService _userPermissionService;
+ private readonly IUserDeleteService _userDeleteService;
+
+ public UserV2Controller(IUserService userService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService)
+ {
+ _userService = userService;
+ _userPermissionService = userPermissionService;
+ _userDeleteService = userDeleteService;
+ }
+
+ /// <summary>
+ /// Get all users.
+ /// </summary>
+ /// <returns>All user list.</returns>
+ [HttpGet]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task<ActionResult<Page<HttpUser>>> ListAsync([FromQuery][PositiveInteger] int? page, [FromQuery][PositiveInteger] int? pageSize)
+ {
+ var p = await _userService.GetUsersV2Async(page ?? 1, pageSize ?? 20);
+ var items = await MapListAsync<HttpUser>(p.Items);
+ return p.WithItems(items);
+ }
+
+ /// <summary>
+ /// Create a new user. You have to be administrator.
+ /// </summary>
+ /// <returns>The new user's info.</returns>
+ [HttpPost]
+ [PermissionAuthorize(UserPermission.UserManagement)]
+ [ProducesResponseType(StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult<HttpUser>> PostAsync([FromBody] HttpUserPostRequest body)
+ {
+ var user = await _userService.CreateUserAsync(
+ new CreateUserParams(body.Username, body.Password) { Nickname = body.Nickname });
+ return CreatedAtAction("Get", new { username = body.Username }, await MapAsync<HttpUser>(user));
+ }
+
+ /// <summary>
+ /// Get a user's info.
+ /// </summary>
+ /// <param name="username">Username of the user.</param>
+ /// <returns>User info.</returns>
+ [HttpGet("{username}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult<HttpUser>> GetAsync([FromRoute][Username] string username)
+ {
+ var id = await _userService.GetUserIdByUsernameAsync(username);
+ var user = await _userService.GetUserAsync(id);
+ return await MapAsync<HttpUser>(user);
+ }
+
+ /// <summary>
+ /// Change a user's property.
+ /// </summary>
+ /// <param name="body"></param>
+ /// <param name="username">Username of the user to change.</param>
+ /// <returns>The new user info.</returns>
+ [HttpPatch("{username}")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult<HttpUser>> PatchAsync([FromBody] HttpUserPatchRequest body, [FromRoute][Username] string username)
+ {
+ var userId = await _userService.GetUserIdByUsernameAsync(username);
+ if (UserHasPermission(UserPermission.UserManagement))
+ {
+ var user = await _userService.ModifyUserAsync(userId, AutoMapperMap<ModifyUserParams>(body));
+ return await MapAsync<HttpUser>(user);
+ }
+ else
+ {
+ if (userId != GetAuthUserId())
+ return Forbid();
+
+ if (body.Username is not null)
+ return Forbid();
+
+ if (body.Password is not null)
+ return Forbid();
+
+ var user = await _userService.ModifyUserAsync(GetAuthUserId(), AutoMapperMap<ModifyUserParams>(body));
+ return await MapAsync<HttpUser>(user);
+ }
+ }
+
+ private const string RootUserInvalidOperationMessage = "Can't do this operation on root user.";
+
+ /// <summary>
+ /// Delete a user and all his related data. You have to be administrator.
+ /// </summary>
+ /// <param name="username">Username of the user to delete.</param>
+ /// <returns>Info of deletion.</returns>
+ [HttpDelete("{username}")]
+ [PermissionAuthorize(UserPermission.UserManagement)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult> DeleteAsync([FromRoute][Username] string username)
+ {
+ try
+ {
+ await _userDeleteService.DeleteUserAsync(username);
+ return NoContent();
+ }
+ catch (EntityNotExistException)
+ {
+ return NoContent();
+ }
+ catch (InvalidOperationOnRootUserException)
+ {
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidOperation, RootUserInvalidOperationMessage));
+ }
+ }
+
+ [HttpPut("{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult> PutUserPermissionAsync([FromRoute][Username] string username, [FromRoute] UserPermission permission)
+ {
+ try
+ {
+ var id = await _userService.GetUserIdByUsernameAsync(username);
+ await _userPermissionService.AddPermissionToUserAsync(id, permission);
+ return NoContent();
+ }
+ catch (InvalidOperationOnRootUserException)
+ {
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidOperation, RootUserInvalidOperationMessage));
+ }
+ }
+
+ [HttpDelete("{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult> DeleteUserPermissionAsync([FromRoute][Username] string username, [FromRoute] UserPermission permission)
+ {
+ try
+ {
+ var id = await _userService.GetUserIdByUsernameAsync(username);
+ await _userPermissionService.RemovePermissionFromUserAsync(id, permission);
+ return NoContent();
+ }
+ catch (InvalidOperationOnRootUserException)
+ {
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidOperation, RootUserInvalidOperationMessage));
+ }
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Controllers/V2/V2ControllerBase.cs b/BackEnd/Timeline/Controllers/V2/V2ControllerBase.cs
new file mode 100644
index 00000000..d6fa0c84
--- /dev/null
+++ b/BackEnd/Timeline/Controllers/V2/V2ControllerBase.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using Timeline.Auth;
+using Timeline.Services.Mapper;
+using Timeline.Services.User;
+
+namespace Timeline.Controllers.V2
+{
+ public class V2ControllerBase : ControllerBase
+ {
+ #region auth
+ protected bool UserHasPermission(UserPermission permission)
+ {
+ return User.HasPermission(permission);
+ }
+
+ protected long? GetOptionalAuthUserId()
+ {
+ return User.GetOptionalUserId();
+ }
+
+ protected long GetAuthUserId()
+ {
+ return GetOptionalAuthUserId() ?? throw new InvalidOperationException(Resource.ExceptionNoUserId);
+ }
+ #endregion
+
+ #region mapper
+ protected IGenericMapper GetMapper()
+ {
+ return HttpContext.RequestServices.GetRequiredService<IGenericMapper>();
+ }
+
+ protected async Task<T> MapAsync<T>(object o)
+ {
+ return await GetMapper().MapAsync<T>(o, Url, User);
+ }
+
+ protected async Task<List<T>> MapListAsync<T>(IEnumerable<object> o)
+ {
+ return await GetMapper().MapListAsync<T>(o, Url, User);
+ }
+
+ protected T AutoMapperMap<T>(object o)
+ {
+ return GetMapper().AutoMapperMap<T>(o);
+ }
+ #endregion
+ }
+}
+