diff options
-rw-r--r-- | BackEnd/Timeline/Controllers/V2/TimelineBookmarkV2Controller.cs (renamed from BackEnd/Timeline/Controllers/TimelineBookmarkV2Controller.cs) | 4 | ||||
-rw-r--r-- | BackEnd/Timeline/Controllers/V2/TimelinePostV2Controller.cs (renamed from BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs) | 4 | ||||
-rw-r--r-- | BackEnd/Timeline/Controllers/V2/TimelineV2Controller.cs (renamed from BackEnd/Timeline/Controllers/TimelineV2Controller.cs) | 4 | ||||
-rw-r--r-- | BackEnd/Timeline/Controllers/V2/UserV2Controller.cs | 181 | ||||
-rw-r--r-- | BackEnd/Timeline/Controllers/V2/V2ControllerBase.cs | 28 | ||||
-rw-r--r-- | BackEnd/Timeline/Services/User/IUserService.cs | 5 | ||||
-rw-r--r-- | BackEnd/Timeline/Services/User/UserService.cs | 15 | ||||
-rw-r--r-- | BackEnd/Timeline/Timeline.csproj | 6 |
8 files changed, 239 insertions, 8 deletions
diff --git a/BackEnd/Timeline/Controllers/TimelineBookmarkV2Controller.cs b/BackEnd/Timeline/Controllers/V2/TimelineBookmarkV2Controller.cs index 2b31f43e..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; diff --git a/BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs b/BackEnd/Timeline/Controllers/V2/TimelinePostV2Controller.cs index c80cda17..aa839abf 100644 --- a/BackEnd/Timeline/Controllers/TimelinePostV2Controller.cs +++ b/BackEnd/Timeline/Controllers/V2/TimelinePostV2Controller.cs @@ -14,11 +14,11 @@ 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; diff --git a/BackEnd/Timeline/Controllers/TimelineV2Controller.cs b/BackEnd/Timeline/Controllers/V2/TimelineV2Controller.cs index 9811cbed..393446f7 100644 --- a/BackEnd/Timeline/Controllers/TimelineV2Controller.cs +++ b/BackEnd/Timeline/Controllers/V2/TimelineV2Controller.cs @@ -10,11 +10,11 @@ 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; diff --git a/BackEnd/Timeline/Controllers/V2/UserV2Controller.cs b/BackEnd/Timeline/Controllers/V2/UserV2Controller.cs new file mode 100644 index 00000000..e556bf8e --- /dev/null +++ b/BackEnd/Timeline/Controllers/V2/UserV2Controller.cs @@ -0,0 +1,181 @@ +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.Mapper; +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; + private readonly IGenericMapper _mapper; + + public UserV2Controller(IUserService userService, IUserPermissionService userPermissionService, IUserDeleteService userDeleteService, IGenericMapper mapper) + { + _userService = userService; + _userPermissionService = userPermissionService; + _userDeleteService = userDeleteService; + _mapper = mapper; + } + + /// <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 _mapper.MapListAsync<HttpUser>(p.Items, Url, User); + 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 _mapper.MapAsync<HttpUser>(user, Url, 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 _mapper.MapAsync<HttpUser>(user, Url, 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>> Patch([FromBody] HttpUserPatchRequest body, [FromRoute][Username] string username) + { + var userId = await _userService.GetUserIdByUsernameAsync(username); + if (UserHasPermission(UserPermission.UserManagement)) + { + var user = await _userService.ModifyUserAsync(userId, _mapper.AutoMapperMap<ModifyUserParams>(body)); + return await _mapper.MapAsync<HttpUser>(user, Url, 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(), _mapper.AutoMapperMap<ModifyUserParams>(body)); + return await _mapper.MapAsync<HttpUser>(user, Url, 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<CommonDeleteResponse>> Delete([FromRoute][Username] string username) + { + try + { + await _userDeleteService.DeleteUserAsync(username); + return NoContent(); + } + catch (InvalidOperationOnRootUserException) + { + return UnprocessableEntity(); + } + } + + [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<CommonResponse>> PutUserPermission([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(); + } + } + + [HttpDelete("{username}/permissions/{permission}"), PermissionAuthorize(UserPermission.UserManagement)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task<ActionResult<CommonResponse>> DeleteUserPermission([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(); + } + } + } +} diff --git a/BackEnd/Timeline/Controllers/V2/V2ControllerBase.cs b/BackEnd/Timeline/Controllers/V2/V2ControllerBase.cs new file mode 100644 index 00000000..54b9c7c9 --- /dev/null +++ b/BackEnd/Timeline/Controllers/V2/V2ControllerBase.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Timeline.Auth; +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 + } +} + diff --git a/BackEnd/Timeline/Services/User/IUserService.cs b/BackEnd/Timeline/Services/User/IUserService.cs index 6ea9a4d2..efb61ccd 100644 --- a/BackEnd/Timeline/Services/User/IUserService.cs +++ b/BackEnd/Timeline/Services/User/IUserService.cs @@ -2,7 +2,8 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Entities;
-
+using Timeline.Models; + namespace Timeline.Services.User
{
public interface IUserService
@@ -46,6 +47,8 @@ namespace Timeline.Services.User /// <returns>The user info of users.</returns>
Task<List<UserEntity>> GetUsersAsync();
+ Task<Page<UserEntity>> GetUsersV2Async(int page, int pageSize);
+
/// <summary>
/// Create a user with given info.
/// </summary>
diff --git a/BackEnd/Timeline/Services/User/UserService.cs b/BackEnd/Timeline/Services/User/UserService.cs index d5ee9a2f..a20076d6 100644 --- a/BackEnd/Timeline/Services/User/UserService.cs +++ b/BackEnd/Timeline/Services/User/UserService.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
+using Timeline.Models; using Timeline.Models.Validation;
using Timeline.Services.Token; @@ -266,6 +267,18 @@ namespace Timeline.Services.User _logger.LogInformation(Resource.LogChangePassowrd, entity.Username, id);
await _userTokenService.RevokeAllTokenByUserIdAsync(id);
- }
+ } + + public async Task<Page<UserEntity>> GetUsersV2Async(int page, int pageSize) + { + if (page <= 0) throw new ArgumentOutOfRangeException(nameof(page)); + if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); + + var items = await _database.Users.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
+
+ var totalCount = await _database.Users.CountAsync();
+
+ return new Page<UserEntity>(page, pageSize, totalCount, items);
+ } }
}
diff --git a/BackEnd/Timeline/Timeline.csproj b/BackEnd/Timeline/Timeline.csproj index ee14de25..5db7a613 100644 --- a/BackEnd/Timeline/Timeline.csproj +++ b/BackEnd/Timeline/Timeline.csproj @@ -199,4 +199,10 @@ <LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
+ <ItemGroup>
+ <None Remove="Controllers\V2\" />
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="Controllers\V2\" />
+ </ItemGroup>
</Project>
\ No newline at end of file |