aboutsummaryrefslogtreecommitdiff
path: root/BackEnd/Timeline
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-01-07 16:23:20 +0800
committercrupest <crupest@outlook.com>2021-01-07 16:23:20 +0800
commitdf1ef1e21d8d889a2c9abd440039533c6a43818f (patch)
treec483a2fff48ad952e787e5af1bb541d939a09f19 /BackEnd/Timeline
parent9470631c67c4740982ff2d8a16cbbb86fdd34609 (diff)
downloadtimeline-df1ef1e21d8d889a2c9abd440039533c6a43818f.tar.gz
timeline-df1ef1e21d8d889a2c9abd440039533c6a43818f.tar.bz2
timeline-df1ef1e21d8d889a2c9abd440039533c6a43818f.zip
史诗级重构!
Diffstat (limited to 'BackEnd/Timeline')
-rw-r--r--BackEnd/Timeline/Auth/MyAuthenticationHandler.cs2
-rw-r--r--BackEnd/Timeline/Controllers/BookmarkTimelineController.cs16
-rw-r--r--BackEnd/Timeline/Controllers/HighlightTimelineController.cs12
-rw-r--r--BackEnd/Timeline/Controllers/TimelineController.cs185
-rw-r--r--BackEnd/Timeline/Controllers/TokenController.cs10
-rw-r--r--BackEnd/Timeline/Controllers/UserController.cs15
-rw-r--r--BackEnd/Timeline/GlobalSuppressions.cs1
-rw-r--r--BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.Designer.cs498
-rw-r--r--BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.cs20
-rw-r--r--BackEnd/Timeline/Models/Http/CommonResponse.cs (renamed from BackEnd/Timeline/Models/Http/Common.cs)10
-rw-r--r--BackEnd/Timeline/Models/Http/ErrorResponse.cs10
-rw-r--r--BackEnd/Timeline/Models/Http/Timeline.cs137
-rw-r--r--BackEnd/Timeline/Models/Http/TimelineController.cs3
-rw-r--r--BackEnd/Timeline/Models/Http/User.cs73
-rw-r--r--BackEnd/Timeline/Models/Mapper/TimelineMapper.cs85
-rw-r--r--BackEnd/Timeline/Models/Mapper/UserMapper.cs38
-rw-r--r--BackEnd/Timeline/Models/Timeline.cs27
-rw-r--r--BackEnd/Timeline/Models/TimelineInfo.cs130
-rw-r--r--BackEnd/Timeline/Models/UserInfo.cs48
-rw-r--r--BackEnd/Timeline/Resources/Services/Exceptions.Designer.cs4
-rw-r--r--BackEnd/Timeline/Resources/Services/Exceptions.resx144
-rw-r--r--BackEnd/Timeline/Services/BasicTimelineService.cs12
-rw-r--r--BackEnd/Timeline/Services/BookmarkTimelineService.cs20
-rw-r--r--BackEnd/Timeline/Services/Exceptions/TimelinePostNotExistException.cs17
-rw-r--r--BackEnd/Timeline/Services/HighlightTimelineService.cs20
-rw-r--r--BackEnd/Timeline/Services/TimelinePostService.cs221
-rw-r--r--BackEnd/Timeline/Services/TimelineService.cs449
-rw-r--r--BackEnd/Timeline/Services/UserService.cs56
-rw-r--r--BackEnd/Timeline/Services/UserTokenManager.cs7
29 files changed, 1239 insertions, 1031 deletions
diff --git a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs
index 223ff187..c57c96bc 100644
--- a/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs
+++ b/BackEnd/Timeline/Auth/MyAuthenticationHandler.cs
@@ -84,7 +84,7 @@ namespace Timeline.Auth
var identity = new ClaimsIdentity(AuthenticationConstants.Scheme);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(CultureInfo.InvariantCulture), ClaimValueTypes.Integer64));
identity.AddClaim(new Claim(identity.NameClaimType, user.Username, ClaimValueTypes.String));
- identity.AddClaims(user.Permissions.Select(permission => new Claim(AuthenticationConstants.PermissionClaimName, permission.ToString(), ClaimValueTypes.String)));
+ identity.AddClaims(user.Permissions.Select(permission => new Claim(AuthenticationConstants.PermissionClaimName, permission.Permission, ClaimValueTypes.String)));
var principal = new ClaimsPrincipal();
principal.AddIdentity(identity);
diff --git a/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs
index 9dff95f3..7412232d 100644
--- a/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs
+++ b/BackEnd/Timeline/Controllers/BookmarkTimelineController.cs
@@ -1,9 +1,9 @@
-using AutoMapper;
-using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Models.Http;
+using Timeline.Models.Mapper;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Services.Exceptions;
@@ -18,13 +18,12 @@ namespace Timeline.Controllers
public class BookmarkTimelineController : Controller
{
private readonly IBookmarkTimelineService _service;
+ private readonly ITimelineService _timelineService;
- private readonly IMapper _mapper;
-
- public BookmarkTimelineController(IBookmarkTimelineService service, IMapper mapper)
+ public BookmarkTimelineController(IBookmarkTimelineService service, ITimelineService timelineService)
{
_service = service;
- _mapper = mapper;
+ _timelineService = timelineService;
}
/// <summary>
@@ -37,8 +36,9 @@ namespace Timeline.Controllers
[ProducesResponseType(401)]
public async Task<ActionResult<List<HttpTimeline>>> List()
{
- var bookmarks = await _service.GetBookmarks(this.GetUserId());
- return Ok(_mapper.Map<List<HttpTimeline>>(bookmarks));
+ var ids = await _service.GetBookmarks(this.GetUserId());
+ var timelines = await _timelineService.GetTimelineList(ids);
+ return Ok(timelines.MapToHttp(Url));
}
/// <summary>
diff --git a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs
index 519d6161..76650b00 100644
--- a/BackEnd/Timeline/Controllers/HighlightTimelineController.cs
+++ b/BackEnd/Timeline/Controllers/HighlightTimelineController.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Auth;
using Timeline.Models.Http;
+using Timeline.Models.Mapper;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Services.Exceptions;
@@ -18,12 +19,12 @@ namespace Timeline.Controllers
public class HighlightTimelineController : Controller
{
private readonly IHighlightTimelineService _service;
- private readonly IMapper _mapper;
+ private readonly ITimelineService _timelineService;
- public HighlightTimelineController(IHighlightTimelineService service, IMapper mapper)
+ public HighlightTimelineController(IHighlightTimelineService service, ITimelineService timelineService)
{
_service = service;
- _mapper = mapper;
+ _timelineService = timelineService;
}
/// <summary>
@@ -34,8 +35,9 @@ namespace Timeline.Controllers
[ProducesResponseType(200)]
public async Task<ActionResult<List<HttpTimeline>>> List()
{
- var t = await _service.GetHighlightTimelines();
- return _mapper.Map<List<HttpTimeline>>(t);
+ var ids = await _service.GetHighlightTimelines();
+ var timelines = await _timelineService.GetTimelineList(ids);
+ return timelines.MapToHttp(Url);
}
/// <summary>
diff --git a/BackEnd/Timeline/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs
index 27b4b7a7..b1401a03 100644
--- a/BackEnd/Timeline/Controllers/TimelineController.cs
+++ b/BackEnd/Timeline/Controllers/TimelineController.cs
@@ -2,15 +2,16 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
+using Timeline.Entities;
using Timeline.Filters;
using Timeline.Helpers;
using Timeline.Models;
using Timeline.Models.Http;
+using Timeline.Models.Mapper;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Services.Exceptions;
@@ -25,8 +26,6 @@ namespace Timeline.Controllers
[ProducesErrorResponseType(typeof(CommonResponse))]
public class TimelineController : Controller
{
- private readonly ILogger<TimelineController> _logger;
-
private readonly IUserService _userService;
private readonly ITimelineService _service;
private readonly ITimelinePostService _postService;
@@ -36,9 +35,8 @@ namespace Timeline.Controllers
/// <summary>
///
/// </summary>
- public TimelineController(ILogger<TimelineController> logger, IUserService userService, ITimelineService service, ITimelinePostService timelinePostService, IMapper mapper)
+ public TimelineController(IUserService userService, ITimelineService service, ITimelinePostService timelinePostService, IMapper mapper)
{
- _logger = logger;
_userService = userService;
_service = service;
_postService = timelinePostService;
@@ -109,44 +107,46 @@ namespace Timeline.Controllers
}
var timelines = await _service.GetTimelines(relationship, visibilityFilter);
- var result = _mapper.Map<List<HttpTimeline>>(timelines);
+ var result = timelines.MapToHttp(Url);
return result;
}
/// <summary>
/// Get info of a timeline.
/// </summary>
- /// <param name="name">The timeline name.</param>
+ /// <param name="timeline">The timeline name.</param>
/// <param name="checkUniqueId">A unique id. If specified and if-modified-since is also specified, the timeline info will return when unique id is not the specified one even if it is not modified.</param>
/// <param name="queryIfModifiedSince">Same effect as If-Modified-Since header and take precedence than it.</param>
/// <param name="headerIfModifiedSince">If specified, will return 304 if not modified.</param>
/// <returns>The timeline info.</returns>
- [HttpGet("timelines/{name}")]
+ [HttpGet("timelines/{timeline}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status304NotModified)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<HttpTimeline>> TimelineGet([FromRoute][GeneralTimelineName] string name, [FromQuery] string? checkUniqueId, [FromQuery(Name = "ifModifiedSince")] DateTime? queryIfModifiedSince, [FromHeader(Name = "If-Modified-Since")] DateTime? headerIfModifiedSince)
+ public async Task<ActionResult<HttpTimeline>> TimelineGet([FromRoute][GeneralTimelineName] string timeline, [FromQuery] string? checkUniqueId, [FromQuery(Name = "ifModifiedSince")] DateTime? queryIfModifiedSince, [FromHeader(Name = "If-Modified-Since")] DateTime? headerIfModifiedSince)
{
DateTime? ifModifiedSince = null;
if (queryIfModifiedSince.HasValue)
{
ifModifiedSince = queryIfModifiedSince.Value;
}
- else if (headerIfModifiedSince != null)
+ else if (headerIfModifiedSince is not null)
{
ifModifiedSince = headerIfModifiedSince.Value;
}
+ var timelineId = await _service.GetTimelineIdByName(timeline);
+
bool returnNotModified = false;
if (ifModifiedSince.HasValue)
{
- var lastModified = await _service.GetTimelineLastModifiedTime(name);
+ var lastModified = await _service.GetTimelineLastModifiedTime(timelineId);
if (lastModified < ifModifiedSince.Value)
{
if (checkUniqueId != null)
{
- var uniqueId = await _service.GetTimelineUniqueId(name);
+ var uniqueId = await _service.GetTimelineUniqueId(timelineId);
if (uniqueId == checkUniqueId)
{
returnNotModified = true;
@@ -165,8 +165,8 @@ namespace Timeline.Controllers
}
else
{
- var timeline = await _service.GetTimeline(name);
- var result = _mapper.Map<HttpTimeline>(timeline);
+ var t = await _service.GetTimeline(timelineId);
+ var result = t.MapToHttp(Url);
return result;
}
}
@@ -174,56 +174,59 @@ namespace Timeline.Controllers
/// <summary>
/// Get posts of a timeline.
/// </summary>
- /// <param name="name">The name of the timeline.</param>
+ /// <param name="timeline">The name of the timeline.</param>
/// <param name="modifiedSince">If set, only posts modified since the time will return.</param>
/// <param name="includeDeleted">If set to true, deleted post will also return.</param>
/// <returns>The post list.</returns>
- [HttpGet("timelines/{name}/posts")]
+ [HttpGet("timelines/{timeline}/posts")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<List<HttpTimelinePost>>> PostListGet([FromRoute][GeneralTimelineName] string name, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted)
+ public async Task<ActionResult<List<HttpTimelinePost>>> PostListGet([FromRoute][GeneralTimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted)
{
- if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(name, this.GetOptionalUserId()))
+ var timelineId = await _service.GetTimelineIdByName(timeline);
+
+ if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(timelineId, this.GetOptionalUserId()))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
- List<TimelinePostInfo> posts = await _postService.GetPosts(name, modifiedSince, includeDeleted ?? false);
+ var posts = await _postService.GetPosts(timelineId, modifiedSince, includeDeleted ?? false);
- var result = _mapper.Map<List<HttpTimelinePost>>(posts);
+ var result = posts.MapToHttp(timeline, Url);
return result;
}
/// <summary>
/// Get the data of a post. Usually a image post.
/// </summary>
- /// <param name="name">Timeline name.</param>
- /// <param name="id">The id of the post.</param>
+ /// <param name="timeline">Timeline name.</param>
+ /// <param name="post">The id of the post.</param>
/// <param name="ifNoneMatch">If-None-Match header.</param>
/// <returns>The data.</returns>
- [HttpGet("timelines/{name}/posts/{id}/data")]
+ [HttpGet("timelines/{timeline}/posts/{post}/data")]
[Produces("image/png", "image/jpeg", "image/gif", "image/webp", "application/json", "text/json")]
[ProducesResponseType(typeof(byte[]), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status304NotModified)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<IActionResult> PostDataGet([FromRoute][GeneralTimelineName] string name, [FromRoute] long id, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch)
+ public async Task<IActionResult> PostDataGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch)
{
_ = ifNoneMatch;
- if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(name, this.GetOptionalUserId()))
+
+ var timelineId = await _service.GetTimelineIdByName(timeline);
+
+ if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(timelineId, this.GetOptionalUserId()))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
try
{
- return await DataCacheHelper.GenerateActionResult(this, () => _postService.GetPostDataETag(name, id), async () =>
- {
- var data = await _postService.GetPostData(name, id);
- return data;
- });
+ return await DataCacheHelper.GenerateActionResult(this,
+ () => _postService.GetPostDataETag(timelineId, post),
+ async () => await _postService.GetPostData(timelineId, post));
}
catch (TimelinePostNotExistException)
{
@@ -238,26 +241,28 @@ namespace Timeline.Controllers
/// <summary>
/// Create a new post.
/// </summary>
- /// <param name="name">Timeline name.</param>
+ /// <param name="timeline">Timeline name.</param>
/// <param name="body"></param>
/// <returns>Info of new post.</returns>
- [HttpPost("timelines/{name}/posts")]
+ [HttpPost("timelines/{timeline}/posts")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<HttpTimelinePost>> PostPost([FromRoute][GeneralTimelineName] string name, [FromBody] HttpTimelinePostCreateRequest body)
+ public async Task<ActionResult<HttpTimelinePost>> PostPost([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body)
{
- var id = this.GetUserId();
- if (!UserHasAllTimelineManagementPermission && !await _service.IsMemberOf(name, id))
+ var timelineId = await _service.GetTimelineIdByName(timeline);
+ var userId = this.GetUserId();
+
+ if (!UserHasAllTimelineManagementPermission && !await _service.IsMemberOf(timelineId, userId))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
var content = body.Content;
- TimelinePostInfo post;
+ TimelinePostEntity post;
if (content.Type == TimelinePostContentTypes.Text)
{
@@ -266,7 +271,7 @@ namespace Timeline.Controllers
{
return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_TextContentTextRequired));
}
- post = await _postService.CreateTextPost(name, id, text, body.Time);
+ post = await _postService.CreateTextPost(timelineId, userId, text, body.Time);
}
else if (content.Type == TimelinePostContentTypes.Image)
{
@@ -287,7 +292,7 @@ namespace Timeline.Controllers
try
{
- post = await _postService.CreateImagePost(name, id, data, body.Time);
+ post = await _postService.CreateImagePost(timelineId, userId, data, body.Time);
}
catch (ImageException)
{
@@ -299,117 +304,128 @@ namespace Timeline.Controllers
return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ContentUnknownType));
}
- var result = _mapper.Map<HttpTimelinePost>(post);
+ var result = post.MapToHttp(timeline, Url);
return result;
}
/// <summary>
/// Delete a post.
/// </summary>
- /// <param name="name">Timeline name.</param>
- /// <param name="id">Post id.</param>
+ /// <param name="timeline">Timeline name.</param>
+ /// <param name="post">Post id.</param>
/// <returns>Info of deletion.</returns>
- [HttpDelete("timelines/{name}/posts/{id}")]
+ [HttpDelete("timelines/{timeline}/posts/{post}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<CommonDeleteResponse>> PostDelete([FromRoute][GeneralTimelineName] string name, [FromRoute] long id)
+ public async Task<ActionResult> PostDelete([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post)
{
- if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermission(name, id, this.GetUserId()))
- {
- return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
- }
+ var timelineId = await _service.GetTimelineIdByName(timeline);
+
try
{
- await _postService.DeletePost(name, id);
- return CommonDeleteResponse.Delete();
+ if (!UserHasAllTimelineManagementPermission && !await _postService.HasPostModifyPermission(timelineId, post, this.GetUserId(), true))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
+ }
+ await _postService.DeletePost(timelineId, post);
+ return Ok();
}
catch (TimelinePostNotExistException)
{
- return CommonDeleteResponse.NotExist();
+ return BadRequest(ErrorResponse.TimelineController.PostNotExist());
}
}
/// <summary>
/// Change properties of a timeline.
/// </summary>
- /// <param name="name">Timeline name.</param>
+ /// <param name="timeline">Timeline name.</param>
/// <param name="body"></param>
/// <returns>The new info.</returns>
- [HttpPatch("timelines/{name}")]
+ [HttpPatch("timelines/{timeline}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<HttpTimeline>> TimelinePatch([FromRoute][GeneralTimelineName] string name, [FromBody] HttpTimelinePatchRequest body)
+ public async Task<ActionResult<HttpTimeline>> TimelinePatch([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePatchRequest body)
{
- if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId())))
+ var timelineId = await _service.GetTimelineIdByName(timeline);
+
+ if (!UserHasAllTimelineManagementPermission && !await _service.HasManagePermission(timelineId, this.GetUserId()))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
- await _service.ChangeProperty(name, _mapper.Map<TimelineChangePropertyRequest>(body));
- var timeline = await _service.GetTimeline(name);
- var result = _mapper.Map<HttpTimeline>(timeline);
+ await _service.ChangeProperty(timelineId, _mapper.Map<TimelineChangePropertyParams>(body));
+ var t = await _service.GetTimeline(timelineId);
+ var result = t.MapToHttp(Url);
return result;
}
/// <summary>
/// Add a member to timeline.
/// </summary>
- /// <param name="name">Timeline name.</param>
+ /// <param name="timeline">Timeline name.</param>
/// <param name="member">The new member's username.</param>
- [HttpPut("timelines/{name}/members/{member}")]
+ [HttpPut("timelines/{timeline}/members/{member}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult> TimelineMemberPut([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member)
+ public async Task<ActionResult<CommonPutResponse>> TimelineMemberPut([FromRoute][GeneralTimelineName] string timeline, [FromRoute][Username] string member)
{
- if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId())))
+ var timelineId = await _service.GetTimelineIdByName(timeline);
+
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId())))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
try
{
- await _service.ChangeMember(name, new List<string> { member }, null);
- return Ok();
+ var userId = await _userService.GetUserIdByUsername(member);
+ var create = await _service.AddMember(timelineId, userId);
+ return Ok(CommonPutResponse.Create(create));
}
catch (UserNotExistException)
{
- return BadRequest(ErrorResponse.TimelineController.MemberPut_NotExist());
+ return BadRequest(ErrorResponse.UserCommon.NotExist());
}
}
/// <summary>
/// Remove a member from timeline.
/// </summary>
- /// <param name="name">Timeline name.</param>
+ /// <param name="timeline">Timeline name.</param>
/// <param name="member">The member's username.</param>
- [HttpDelete("timelines/{name}/members/{member}")]
+ [HttpDelete("timelines/{timeline}/members/{member}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult> TimelineMemberDelete([FromRoute][GeneralTimelineName] string name, [FromRoute][Username] string member)
+ public async Task<ActionResult> TimelineMemberDelete([FromRoute][GeneralTimelineName] string timeline, [FromRoute][Username] string member)
{
- if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId())))
+ var timelineId = await _service.GetTimelineIdByName(timeline);
+
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId())))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
try
{
- await _service.ChangeMember(name, null, new List<string> { member });
- return Ok(CommonDeleteResponse.Delete());
+ var userId = await _userService.GetUserIdByUsername(member);
+ var delete = await _service.RemoveMember(timelineId, userId);
+ return Ok(CommonDeleteResponse.Create(delete));
}
catch (UserNotExistException)
{
- return Ok(CommonDeleteResponse.NotExist());
+ return BadRequest(ErrorResponse.UserCommon.NotExist());
}
}
@@ -430,7 +446,7 @@ namespace Timeline.Controllers
try
{
var timeline = await _service.CreateTimeline(body.Name, userId);
- var result = _mapper.Map<HttpTimeline>(timeline);
+ var result = timeline.MapToHttp(Url);
return result;
}
catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.Timeline)
@@ -442,29 +458,31 @@ namespace Timeline.Controllers
/// <summary>
/// Delete a timeline.
/// </summary>
- /// <param name="name">Timeline name.</param>
+ /// <param name="timeline">Timeline name.</param>
/// <returns>Info of deletion.</returns>
- [HttpDelete("timelines/{name}")]
+ [HttpDelete("timelines/{timeline}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
- public async Task<ActionResult<CommonDeleteResponse>> TimelineDelete([FromRoute][TimelineName] string name)
+ public async Task<ActionResult> TimelineDelete([FromRoute][TimelineName] string timeline)
{
- if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(name, this.GetUserId())))
+ var timelineId = await _service.GetTimelineIdByName(timeline);
+
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId())))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
try
{
- await _service.DeleteTimeline(name);
- return CommonDeleteResponse.Delete();
+ await _service.DeleteTimeline(timelineId);
+ return Ok();
}
catch (TimelineNotExistException)
{
- return CommonDeleteResponse.NotExist();
+ return BadRequest(ErrorResponse.TimelineController.NotExist());
}
}
@@ -476,15 +494,18 @@ namespace Timeline.Controllers
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<HttpTimeline>> TimelineOpChangeName([FromBody] HttpTimelineChangeNameRequest body)
{
- if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(body.OldName, this.GetUserId())))
+ var timelineId = await _service.GetTimelineIdByName(body.OldName);
+
+ if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId())))
{
return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
}
try
{
- var timeline = await _service.ChangeTimelineName(body.OldName, body.NewName);
- return Ok(_mapper.Map<HttpTimeline>(timeline));
+ await _service.ChangeTimelineName(timelineId, body.NewName);
+ var timeline = await _service.GetTimeline(timelineId);
+ return Ok(timeline.MapToHttp(Url));
}
catch (EntityAlreadyExistException)
{
diff --git a/BackEnd/Timeline/Controllers/TokenController.cs b/BackEnd/Timeline/Controllers/TokenController.cs
index c801b8cc..e695a10e 100644
--- a/BackEnd/Timeline/Controllers/TokenController.cs
+++ b/BackEnd/Timeline/Controllers/TokenController.cs
@@ -8,6 +8,7 @@ using System.Globalization;
using System.Threading.Tasks;
using Timeline.Helpers;
using Timeline.Models.Http;
+using Timeline.Models.Mapper;
using Timeline.Services;
using Timeline.Services.Exceptions;
using static Timeline.Resources.Controllers.TokenController;
@@ -27,16 +28,13 @@ namespace Timeline.Controllers
private readonly ILogger<TokenController> _logger;
private readonly IClock _clock;
- private readonly IMapper _mapper;
-
/// <summary></summary>
- public TokenController(IUserCredentialService userCredentialService, IUserTokenManager userTokenManager, ILogger<TokenController> logger, IClock clock, IMapper mapper)
+ public TokenController(IUserCredentialService userCredentialService, IUserTokenManager userTokenManager, ILogger<TokenController> logger, IClock clock)
{
_userCredentialService = userCredentialService;
_userTokenManager = userTokenManager;
_logger = logger;
_clock = clock;
- _mapper = mapper;
}
/// <summary>
@@ -74,7 +72,7 @@ namespace Timeline.Controllers
return Ok(new HttpCreateTokenResponse
{
Token = result.Token,
- User = _mapper.Map<HttpUser>(result.User)
+ User = result.User.MapToHttp(Url)
});
}
catch (UserNotExistException e)
@@ -115,7 +113,7 @@ namespace Timeline.Controllers
("Username", result.Username), ("Token", request.Token)));
return Ok(new HttpVerifyTokenResponse
{
- User = _mapper.Map<HttpUser>(result)
+ User = result.MapToHttp(Url)
});
}
catch (UserTokenTimeExpireException e)
diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs
index 3727da36..93b17b2e 100644
--- a/BackEnd/Timeline/Controllers/UserController.cs
+++ b/BackEnd/Timeline/Controllers/UserController.cs
@@ -3,12 +3,11 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
-using System.Linq;
using System.Threading.Tasks;
using Timeline.Auth;
using Timeline.Helpers;
-using Timeline.Models;
using Timeline.Models.Http;
+using Timeline.Models.Mapper;
using Timeline.Models.Validation;
using Timeline.Services;
using Timeline.Services.Exceptions;
@@ -42,8 +41,6 @@ namespace Timeline.Controllers
_mapper = mapper;
}
- private HttpUser ConvertToUserInfo(UserInfo user) => _mapper.Map<HttpUser>(user);
-
private bool UserHasUserManagementPermission => this.UserHasPermission(UserPermission.UserManagement);
/// <summary>
@@ -55,7 +52,7 @@ namespace Timeline.Controllers
public async Task<ActionResult<HttpUser[]>> List()
{
var users = await _userService.GetUsers();
- var result = users.Select(u => ConvertToUserInfo(u)).ToArray();
+ var result = users.MapToHttp(Url);
return Ok(result);
}
@@ -73,7 +70,7 @@ namespace Timeline.Controllers
{
var id = await _userService.GetUserIdByUsername(username);
var user = await _userService.GetUser(id);
- return Ok(ConvertToUserInfo(user));
+ return Ok(user.MapToHttp(Url));
}
catch (UserNotExistException e)
{
@@ -102,7 +99,7 @@ namespace Timeline.Controllers
{
var id = await _userService.GetUserIdByUsername(username);
var user = await _userService.ModifyUser(id, _mapper.Map<ModifyUserParams>(body));
- return Ok(ConvertToUserInfo(user));
+ return Ok(user.MapToHttp(Url));
}
catch (UserNotExistException e)
{
@@ -129,7 +126,7 @@ namespace Timeline.Controllers
ErrorResponse.Common.CustomMessage_Forbid(UserController_Patch_Forbid_Password));
var user = await _userService.ModifyUser(this.GetUserId(), _mapper.Map<ModifyUserParams>(body));
- return Ok(ConvertToUserInfo(user));
+ return Ok(user.MapToHttp(Url));
}
}
@@ -173,7 +170,7 @@ namespace Timeline.Controllers
try
{
var user = await _userService.CreateUser(body.Username, body.Password);
- return Ok(ConvertToUserInfo(user));
+ return Ok(user.MapToHttp(Url));
}
catch (EntityAlreadyExistException e) when (e.EntityName == EntityNames.User)
{
diff --git a/BackEnd/Timeline/GlobalSuppressions.cs b/BackEnd/Timeline/GlobalSuppressions.cs
index 178ae09c..1d6905a5 100644
--- a/BackEnd/Timeline/GlobalSuppressions.cs
+++ b/BackEnd/Timeline/GlobalSuppressions.cs
@@ -13,3 +13,4 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "That's unnecessary.")]
[assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Redundant")]
[assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "No localization demand.")]
+[assembly: SuppressMessage("Design", "CA1054:URI-like parameters should not be strings")]
diff --git a/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.Designer.cs b/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.Designer.cs
new file mode 100644
index 00000000..6cd41998
--- /dev/null
+++ b/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.Designer.cs
@@ -0,0 +1,498 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Timeline.Entities;
+
+namespace Timeline.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20210107074715_AddRootUserPermissions")]
+ partial class AddRootUserPermissions
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.0");
+
+ modelBuilder.Entity("Timeline.Entities.BookmarkTimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long>("Rank")
+ .HasColumnType("INTEGER")
+ .HasColumnName("rank");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("bookmark_timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.DataEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
+
+ b.Property<int>("Ref")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ref");
+
+ b.Property<string>("Tag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("tag");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tag")
+ .IsUnique();
+
+ b.ToTable("data");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("AddTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("add_time");
+
+ b.Property<long?>("OperatorId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("operator_id");
+
+ b.Property<long>("Order")
+ .HasColumnType("INTEGER")
+ .HasColumnName("order");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OperatorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("highlight_timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Key")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("key");
+
+ b.HasKey("Id");
+
+ b.ToTable("jwt_token");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("CreateTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time");
+
+ b.Property<long>("CurrentPostLocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("current_post_local_id");
+
+ b.Property<string>("Description")
+ .HasColumnType("TEXT")
+ .HasColumnName("description");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Name")
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.Property<DateTime>("NameLastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("name_last_modified");
+
+ b.Property<long>("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("owner");
+
+ b.Property<string>("Title")
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<int>("Visibility")
+ .HasColumnType("INTEGER")
+ .HasColumnName("visibility");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("timeline_members");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long?>("AuthorId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("author");
+
+ b.Property<string>("Content")
+ .HasColumnType("TEXT")
+ .HasColumnName("content");
+
+ b.Property<string>("ContentType")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("content_type");
+
+ b.Property<string>("ExtraContent")
+ .HasColumnType("TEXT")
+ .HasColumnName("extra_content");
+
+ b.Property<DateTime>("LastUpdated")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_updated");
+
+ b.Property<long>("LocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("local_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("timeline_posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("DataTag")
+ .HasColumnType("TEXT")
+ .HasColumnName("data_tag");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Type")
+ .HasColumnType("TEXT")
+ .HasColumnName("type");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("user_avatars");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("CreateTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<DateTime>("LastModified")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<string>("Nickname")
+ .HasColumnType("TEXT")
+ .HasColumnName("nickname");
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("password");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("username");
+
+ b.Property<DateTime>("UsernameChangeTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("username_change_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0L)
+ .HasColumnName("version");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("Permission")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("permission");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_permission");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.BookmarkTimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany()
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Timeline");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Operator")
+ .WithMany()
+ .HasForeignKey("OperatorId");
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany()
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Operator");
+
+ b.Navigation("Timeline");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
+ .WithMany("Timelines")
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Members")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("TimelinesJoined")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Timeline");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
+ .WithMany("TimelinePosts")
+ .HasForeignKey("AuthorId");
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Posts")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Author");
+
+ b.Navigation("Timeline");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithOne("Avatar")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("Permissions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Navigation("Members");
+
+ b.Navigation("Posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Navigation("Avatar");
+
+ b.Navigation("Permissions");
+
+ b.Navigation("TimelinePosts");
+
+ b.Navigation("Timelines");
+
+ b.Navigation("TimelinesJoined");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.cs b/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.cs
new file mode 100644
index 00000000..ff5db722
--- /dev/null
+++ b/BackEnd/Timeline/Migrations/20210107074715_AddRootUserPermissions.cs
@@ -0,0 +1,20 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using Timeline.Services;
+
+namespace Timeline.Migrations
+{
+ public partial class AddRootUserPermissions : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.InsertData("user_permission", new string[] { "user_id", "permission" }, new object[] { 1, UserPermission.UserManagement.ToString() });
+ migrationBuilder.InsertData("user_permission", new string[] { "user_id", "permission" }, new object[] { 1, UserPermission.AllTimelineManagement.ToString() });
+ migrationBuilder.InsertData("user_permission", new string[] { "user_id", "permission" }, new object[] { 1, UserPermission.HighlightTimelineManagement.ToString() });
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/Common.cs b/BackEnd/Timeline/Models/Http/CommonResponse.cs
index 2101a1bb..3d0ed509 100644
--- a/BackEnd/Timeline/Models/Http/Common.cs
+++ b/BackEnd/Timeline/Models/Http/CommonResponse.cs
@@ -60,6 +60,11 @@ namespace Timeline.Models.Http
}
+ internal static CommonPutResponse Create(bool create)
+ {
+ return new CommonPutResponse(0, MessagePutCreate, create);
+ }
+
internal static CommonPutResponse Create()
{
return new CommonPutResponse(0, MessagePutCreate, true);
@@ -107,6 +112,11 @@ namespace Timeline.Models.Http
}
+ internal static CommonDeleteResponse Create(bool delete)
+ {
+ return new CommonDeleteResponse(0, MessageDeleteDelete, delete);
+ }
+
internal static CommonDeleteResponse Delete()
{
return new CommonDeleteResponse(0, MessageDeleteDelete, true);
diff --git a/BackEnd/Timeline/Models/Http/ErrorResponse.cs b/BackEnd/Timeline/Models/Http/ErrorResponse.cs
index 10755fd1..1bc46680 100644
--- a/BackEnd/Timeline/Models/Http/ErrorResponse.cs
+++ b/BackEnd/Timeline/Models/Http/ErrorResponse.cs
@@ -234,16 +234,6 @@ namespace Timeline.Models.Http
return new CommonResponse(ErrorCodes.TimelineController.NotExist, string.Format(message, formatArgs));
}
- public static CommonResponse MemberPut_NotExist(params object?[] formatArgs)
- {
- return new CommonResponse(ErrorCodes.TimelineController.MemberPut_NotExist, string.Format(TimelineController_MemberPut_NotExist, formatArgs));
- }
-
- public static CommonResponse CustomMessage_MemberPut_NotExist(string message, params object?[] formatArgs)
- {
- return new CommonResponse(ErrorCodes.TimelineController.MemberPut_NotExist, string.Format(message, formatArgs));
- }
-
public static CommonResponse QueryRelateNotExist(params object?[] formatArgs)
{
return new CommonResponse(ErrorCodes.TimelineController.QueryRelateNotExist, string.Format(TimelineController_QueryRelateNotExist, formatArgs));
diff --git a/BackEnd/Timeline/Models/Http/Timeline.cs b/BackEnd/Timeline/Models/Http/Timeline.cs
index 8e3831e1..06fa4e5a 100644
--- a/BackEnd/Timeline/Models/Http/Timeline.cs
+++ b/BackEnd/Timeline/Models/Http/Timeline.cs
@@ -1,10 +1,5 @@
-using AutoMapper;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Infrastructure;
-using Microsoft.AspNetCore.Mvc.Routing;
-using System;
+using System;
using System.Collections.Generic;
-using Timeline.Controllers;
namespace Timeline.Models.Http
{
@@ -13,6 +8,16 @@ namespace Timeline.Models.Http
/// </summary>
public class HttpTimelinePostContent
{
+ public HttpTimelinePostContent() { }
+
+ public HttpTimelinePostContent(string type, string? text, string? url, string? eTag)
+ {
+ Type = type;
+ Text = text;
+ Url = url;
+ ETag = eTag;
+ }
+
/// <summary>
/// Type of the post content.
/// </summary>
@@ -36,6 +41,18 @@ namespace Timeline.Models.Http
/// </summary>
public class HttpTimelinePost
{
+ public HttpTimelinePost() { }
+
+ public HttpTimelinePost(long id, HttpTimelinePostContent? content, bool deleted, DateTime time, HttpUser? author, DateTime lastUpdated)
+ {
+ Id = id;
+ Content = content;
+ Deleted = deleted;
+ Time = time;
+ Author = author;
+ LastUpdated = lastUpdated;
+ }
+
/// <summary>
/// Post id.
/// </summary>
@@ -67,6 +84,23 @@ namespace Timeline.Models.Http
/// </summary>
public class HttpTimeline
{
+ public HttpTimeline() { }
+
+ public HttpTimeline(string uniqueId, string title, string name, DateTime nameLastModifed, string description, HttpUser owner, TimelineVisibility visibility, List<HttpUser> members, DateTime createTime, DateTime lastModified, HttpTimelineLinks links)
+ {
+ UniqueId = uniqueId;
+ Title = title;
+ Name = name;
+ NameLastModifed = nameLastModifed;
+ Description = description;
+ Owner = owner;
+ Visibility = visibility;
+ Members = members;
+ CreateTime = createTime;
+ LastModified = lastModified;
+ _links = links;
+ }
+
/// <summary>
/// Unique id.
/// </summary>
@@ -123,6 +157,14 @@ namespace Timeline.Models.Http
/// </summary>
public class HttpTimelineLinks
{
+ public HttpTimelineLinks() { }
+
+ public HttpTimelineLinks(string self, string posts)
+ {
+ Self = self;
+ Posts = posts;
+ }
+
/// <summary>
/// Self.
/// </summary>
@@ -132,87 +174,4 @@ namespace Timeline.Models.Http
/// </summary>
public string Posts { get; set; } = default!;
}
-
- public class HttpTimelineLinksValueResolver : IValueResolver<TimelineInfo, HttpTimeline, HttpTimelineLinks>
- {
- private readonly IActionContextAccessor _actionContextAccessor;
- private readonly IUrlHelperFactory _urlHelperFactory;
-
- public HttpTimelineLinksValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory)
- {
- _actionContextAccessor = actionContextAccessor;
- _urlHelperFactory = urlHelperFactory;
- }
-
- public HttpTimelineLinks Resolve(TimelineInfo source, HttpTimeline destination, HttpTimelineLinks destMember, ResolutionContext context)
- {
- var actionContext = _actionContextAccessor.AssertActionContextForUrlFill();
- var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext);
-
- return new HttpTimelineLinks
- {
- Self = urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { source.Name }),
- Posts = urlHelper.ActionLink(nameof(TimelineController.PostListGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { source.Name })
- };
- }
- }
-
- public class HttpTimelinePostContentResolver : IValueResolver<TimelinePostInfo, HttpTimelinePost, HttpTimelinePostContent?>
- {
- private readonly IActionContextAccessor _actionContextAccessor;
- private readonly IUrlHelperFactory _urlHelperFactory;
-
- public HttpTimelinePostContentResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory)
- {
- _actionContextAccessor = actionContextAccessor;
- _urlHelperFactory = urlHelperFactory;
- }
-
- public HttpTimelinePostContent? Resolve(TimelinePostInfo source, HttpTimelinePost destination, HttpTimelinePostContent? destMember, ResolutionContext context)
- {
- var actionContext = _actionContextAccessor.AssertActionContextForUrlFill();
- var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext);
-
- var sourceContent = source.Content;
-
- if (sourceContent == null)
- {
- return null;
- }
-
- if (sourceContent is TextTimelinePostContent textContent)
- {
- return new HttpTimelinePostContent
- {
- Type = TimelinePostContentTypes.Text,
- Text = textContent.Text
- };
- }
- else if (sourceContent is ImageTimelinePostContent imageContent)
- {
- return new HttpTimelinePostContent
- {
- Type = TimelinePostContentTypes.Image,
- Url = urlHelper.ActionLink(
- action: nameof(TimelineController.PostDataGet),
- controller: nameof(TimelineController)[0..^nameof(Controller).Length],
- values: new { Name = source.TimelineName, Id = source.Id }),
- ETag = $"\"{imageContent.DataTag}\""
- };
- }
- else
- {
- throw new InvalidOperationException(Resources.Models.Http.Exception.UnknownPostContentType);
- }
- }
- }
-
- public class HttpTimelineAutoMapperProfile : Profile
- {
- public HttpTimelineAutoMapperProfile()
- {
- CreateMap<TimelineInfo, HttpTimeline>().ForMember(u => u._links, opt => opt.MapFrom<HttpTimelineLinksValueResolver>());
- CreateMap<TimelinePostInfo, HttpTimelinePost>().ForMember(p => p.Content, opt => opt.MapFrom<HttpTimelinePostContentResolver>());
- }
- }
}
diff --git a/BackEnd/Timeline/Models/Http/TimelineController.cs b/BackEnd/Timeline/Models/Http/TimelineController.cs
index 42a926fd..f6039b35 100644
--- a/BackEnd/Timeline/Models/Http/TimelineController.cs
+++ b/BackEnd/Timeline/Models/Http/TimelineController.cs
@@ -2,6 +2,7 @@
using System;
using System.ComponentModel.DataAnnotations;
using Timeline.Models.Validation;
+using Timeline.Services;
namespace Timeline.Models.Http
{
@@ -96,7 +97,7 @@ namespace Timeline.Models.Http
{
public HttpTimelineControllerAutoMapperProfile()
{
- CreateMap<HttpTimelinePatchRequest, TimelineChangePropertyRequest>();
+ CreateMap<HttpTimelinePatchRequest, TimelineChangePropertyParams>();
}
}
}
diff --git a/BackEnd/Timeline/Models/Http/User.cs b/BackEnd/Timeline/Models/Http/User.cs
index bdb40b9f..994c08bf 100644
--- a/BackEnd/Timeline/Models/Http/User.cs
+++ b/BackEnd/Timeline/Models/Http/User.cs
@@ -1,10 +1,4 @@
-using AutoMapper;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Infrastructure;
-using Microsoft.AspNetCore.Mvc.Routing;
-using System.Collections.Generic;
-using Timeline.Controllers;
-using Timeline.Services;
+using System.Collections.Generic;
namespace Timeline.Models.Http
{
@@ -13,6 +7,17 @@ namespace Timeline.Models.Http
/// </summary>
public class HttpUser
{
+ public HttpUser() { }
+
+ public HttpUser(string uniqueId, string username, string nickname, List<string> permissions, HttpUserLinks links)
+ {
+ UniqueId = uniqueId;
+ Username = username;
+ Nickname = nickname;
+ Permissions = permissions;
+ _links = links;
+ }
+
/// <summary>
/// Unique id.
/// </summary>
@@ -44,6 +49,15 @@ namespace Timeline.Models.Http
/// </summary>
public class HttpUserLinks
{
+ public HttpUserLinks() { }
+
+ public HttpUserLinks(string self, string avatar, string timeline)
+ {
+ Self = self;
+ Avatar = avatar;
+ Timeline = timeline;
+ }
+
/// <summary>
/// Self.
/// </summary>
@@ -57,49 +71,4 @@ namespace Timeline.Models.Http
/// </summary>
public string Timeline { get; set; } = default!;
}
-
- public class HttpUserPermissionsValueConverter : ITypeConverter<UserPermissions, List<string>>
- {
- public List<string> Convert(UserPermissions source, List<string> destination, ResolutionContext context)
- {
- return source.ToStringList();
- }
- }
-
- public class HttpUserLinksValueResolver : IValueResolver<UserInfo, HttpUser, HttpUserLinks>
- {
- private readonly IActionContextAccessor _actionContextAccessor;
- private readonly IUrlHelperFactory _urlHelperFactory;
-
- public HttpUserLinksValueResolver(IActionContextAccessor actionContextAccessor, IUrlHelperFactory urlHelperFactory)
- {
- _actionContextAccessor = actionContextAccessor;
- _urlHelperFactory = urlHelperFactory;
- }
-
- public HttpUserLinks Resolve(UserInfo source, HttpUser destination, HttpUserLinks destMember, ResolutionContext context)
- {
- var actionContext = _actionContextAccessor.AssertActionContextForUrlFill();
- var urlHelper = _urlHelperFactory.GetUrlHelper(actionContext);
-
- var result = new HttpUserLinks
- {
- Self = urlHelper.ActionLink(nameof(UserController.Get), nameof(UserController)[0..^nameof(Controller).Length], new { destination.Username }),
- Avatar = urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController)[0..^nameof(Controller).Length], new { destination.Username }),
- Timeline = urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { Name = "@" + destination.Username })
- };
- return result;
- }
- }
-
- public class HttpUserAutoMapperProfile : Profile
- {
- public HttpUserAutoMapperProfile()
- {
- CreateMap<UserPermissions, List<string>>()
- .ConvertUsing<HttpUserPermissionsValueConverter>();
- CreateMap<UserInfo, HttpUser>()
- .ForMember(u => u._links, opt => opt.MapFrom<HttpUserLinksValueResolver>());
- }
- }
}
diff --git a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs
new file mode 100644
index 00000000..89a5c0c8
--- /dev/null
+++ b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs
@@ -0,0 +1,85 @@
+using AutoMapper;
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading.Tasks;
+using Timeline.Controllers;
+using Timeline.Entities;
+using Timeline.Models.Http;
+using Timeline.Services;
+
+namespace Timeline.Models.Mapper
+{
+ public static class TimelineMapper
+ {
+ public static HttpTimeline MapToHttp(this TimelineEntity entity, IUrlHelper urlHelper)
+ {
+ var timelineName = entity.Name is null ? "@" + entity.Owner.Username : entity.Name;
+
+ return new HttpTimeline(
+ uniqueId: entity.UniqueId,
+ title: string.IsNullOrEmpty(entity.Title) ? timelineName : entity.Title,
+ name: timelineName,
+ nameLastModifed: entity.NameLastModified,
+ description: entity.Description ?? "",
+ owner: entity.Owner.MapToHttp(urlHelper),
+ visibility: entity.Visibility,
+ members: entity.Members.Select(m => m.User.MapToHttp(urlHelper)).ToList(),
+ createTime: entity.CreateTime,
+ lastModified: entity.LastModified,
+ links: new HttpTimelineLinks(
+ self: urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName }),
+ posts: urlHelper.ActionLink(nameof(TimelineController.PostListGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName })
+ )
+ );
+ }
+
+ public static List<HttpTimeline> MapToHttp(this List<TimelineEntity> entites, IUrlHelper urlHelper)
+ {
+ return entites.Select(e => e.MapToHttp(urlHelper)).ToList();
+ }
+
+
+ public static HttpTimelinePost MapToHttp(this TimelinePostEntity entity, string timelineName, IUrlHelper urlHelper)
+ {
+ HttpTimelinePostContent? content = null;
+
+ if (entity.Content != null)
+ {
+ content = entity.ContentType switch
+ {
+ TimelinePostContentTypes.Text => new HttpTimelinePostContent
+ (
+ type: TimelinePostContentTypes.Text,
+ text: entity.Content,
+ url: null,
+ eTag: null
+ ),
+ TimelinePostContentTypes.Image => new HttpTimelinePostContent
+ (
+ type: TimelinePostContentTypes.Image,
+ text: null,
+ url: urlHelper.ActionLink(nameof(TimelineController.PostDataGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName, post = entity.LocalId }),
+ eTag: $"\"{entity.Content}\""
+ ),
+ _ => throw new DatabaseCorruptedException(string.Format(CultureInfo.InvariantCulture, "Unknown timeline post type {0}.", entity.ContentType))
+ };
+ }
+
+ return new HttpTimelinePost(
+ id: entity.LocalId,
+ content: content,
+ deleted: content is null,
+ time: entity.Time,
+ author: entity.Author?.MapToHttp(urlHelper),
+ lastUpdated: entity.LastUpdated
+ );
+ }
+
+ public static List<HttpTimelinePost> MapToHttp(this List<TimelinePostEntity> entities, string timelineName, IUrlHelper urlHelper)
+ {
+ return entities.Select(e => e.MapToHttp(timelineName, urlHelper)).ToList();
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Mapper/UserMapper.cs b/BackEnd/Timeline/Models/Mapper/UserMapper.cs
new file mode 100644
index 00000000..3255dca9
--- /dev/null
+++ b/BackEnd/Timeline/Models/Mapper/UserMapper.cs
@@ -0,0 +1,38 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Collections.Generic;
+using System.Linq;
+using Timeline.Controllers;
+using Timeline.Entities;
+using Timeline.Models.Http;
+using Timeline.Services;
+
+namespace Timeline.Models.Mapper
+{
+ public static class UserMapper
+ {
+ public static HttpUser MapToHttp(this UserEntity entity, IUrlHelper urlHelper)
+ {
+ return new HttpUser(
+ uniqueId: entity.UniqueId,
+ username: entity.Username,
+ nickname: string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname,
+ permissions: MapPermission(entity),
+ links: new HttpUserLinks(
+ self: urlHelper.ActionLink(nameof(UserController.Get), nameof(UserController)[0..^nameof(Controller).Length], new { entity.Username }),
+ avatar: urlHelper.ActionLink(nameof(UserAvatarController.Get), nameof(UserAvatarController)[0..^nameof(Controller).Length], new { entity.Username }),
+ timeline: urlHelper.ActionLink(nameof(TimelineController.TimelineGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = "@" + entity.Username })
+ )
+ );
+ }
+
+ public static List<HttpUser> MapToHttp(this List<UserEntity> entities, IUrlHelper urlHelper)
+ {
+ return entities.Select(e => e.MapToHttp(urlHelper)).ToList();
+ }
+
+ private static List<string> MapPermission(UserEntity entity)
+ {
+ return entity.Permissions.Select(p => p.Permission).ToList();
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Timeline.cs b/BackEnd/Timeline/Models/Timeline.cs
new file mode 100644
index 00000000..fa3c0eb3
--- /dev/null
+++ b/BackEnd/Timeline/Models/Timeline.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+
+namespace Timeline.Models
+{
+ public enum TimelineVisibility
+ {
+ /// <summary>
+ /// All people including those without accounts.
+ /// </summary>
+ Public,
+ /// <summary>
+ /// Only people signed in.
+ /// </summary>
+ Register,
+ /// <summary>
+ /// Only member.
+ /// </summary>
+ Private
+ }
+
+ public static class TimelinePostContentTypes
+ {
+ public const string Text = "text";
+ public const string Image = "image";
+ }
+}
diff --git a/BackEnd/Timeline/Models/TimelineInfo.cs b/BackEnd/Timeline/Models/TimelineInfo.cs
deleted file mode 100644
index 649af274..00000000
--- a/BackEnd/Timeline/Models/TimelineInfo.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Timeline.Models
-{
- public enum TimelineVisibility
- {
- /// <summary>
- /// All people including those without accounts.
- /// </summary>
- Public,
- /// <summary>
- /// Only people signed in.
- /// </summary>
- Register,
- /// <summary>
- /// Only member.
- /// </summary>
- Private
- }
-
- public static class TimelinePostContentTypes
- {
- public const string Text = "text";
- public const string Image = "image";
- }
-
- public interface ITimelinePostContent
- {
- public string Type { get; }
- }
-
- public class TextTimelinePostContent : ITimelinePostContent
- {
- public TextTimelinePostContent(string text) { Text = text; }
-
- public string Type { get; } = TimelinePostContentTypes.Text;
- public string Text { get; set; }
- }
-
- public class ImageTimelinePostContent : ITimelinePostContent
- {
- public ImageTimelinePostContent(string dataTag) { DataTag = dataTag; }
-
- public string Type { get; } = TimelinePostContentTypes.Image;
-
- /// <summary>
- /// The tag of the data. The tag of the entry in DataManager. Also the etag (not quoted).
- /// </summary>
- public string DataTag { get; set; }
- }
-
- public class TimelinePostInfo
- {
- public TimelinePostInfo()
- {
-
- }
-
- public TimelinePostInfo(long id, ITimelinePostContent? content, DateTime time, UserInfo? author, DateTime lastUpdated, string timelineName)
- {
- Id = id;
- Content = content;
- Time = time;
- Author = author;
- LastUpdated = lastUpdated;
- TimelineName = timelineName;
- }
-
- public long Id { get; set; }
- public ITimelinePostContent? Content { get; set; }
- public bool Deleted => Content == null;
- public DateTime Time { get; set; }
- public UserInfo? Author { get; set; }
- public DateTime LastUpdated { get; set; }
- public string TimelineName { get; set; } = default!;
- }
-
- public class TimelineInfo
- {
- public TimelineInfo()
- {
-
- }
-
- public TimelineInfo(
- string uniqueId,
- string name,
- DateTime nameLastModified,
- string title,
- string description,
- UserInfo owner,
- TimelineVisibility visibility,
- List<UserInfo> members,
- DateTime createTime,
- DateTime lastModified)
- {
- UniqueId = uniqueId;
- Name = name;
- NameLastModified = nameLastModified;
- Title = title;
- Description = description;
- Owner = owner;
- Visibility = visibility;
- Members = members;
- CreateTime = createTime;
- LastModified = lastModified;
- }
-
- public string UniqueId { get; set; } = default!;
- public string Name { get; set; } = default!;
- public DateTime NameLastModified { get; set; } = default!;
- public string Title { get; set; } = default!;
- public string Description { get; set; } = default!;
- public UserInfo Owner { get; set; } = default!;
- public TimelineVisibility Visibility { get; set; }
-#pragma warning disable CA2227 // Collection properties should be read only
- public List<UserInfo> Members { get; set; } = default!;
-#pragma warning restore CA2227 // Collection properties should be read only
- public DateTime CreateTime { get; set; } = default!;
- public DateTime LastModified { get; set; } = default!;
- }
-
- public class TimelineChangePropertyRequest
- {
- public string? Title { get; set; }
- public string? Description { get; set; }
- public TimelineVisibility? Visibility { get; set; }
- }
-}
diff --git a/BackEnd/Timeline/Models/UserInfo.cs b/BackEnd/Timeline/Models/UserInfo.cs
deleted file mode 100644
index e8d57def..00000000
--- a/BackEnd/Timeline/Models/UserInfo.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using Timeline.Services;
-
-namespace Timeline.Models
-{
- public class UserInfo
- {
- public UserInfo()
- {
-
- }
-
- public UserInfo(
- long id,
- string uniqueId,
- string username,
- string nickname,
- UserPermissions permissions,
- DateTime usernameChangeTime,
- DateTime createTime,
- DateTime lastModified,
- long version)
- {
- Id = id;
- UniqueId = uniqueId;
- Username = username;
- Nickname = nickname;
- Permissions = permissions;
- UsernameChangeTime = usernameChangeTime;
- CreateTime = createTime;
- LastModified = lastModified;
- Version = version;
- }
-
- public long Id { get; set; }
- public string UniqueId { get; set; } = default!;
-
- public string Username { get; set; } = default!;
- public string Nickname { get; set; } = default!;
-
- public UserPermissions Permissions { get; set; } = default!;
-
- public DateTime UsernameChangeTime { get; set; }
- public DateTime CreateTime { get; set; }
- public DateTime LastModified { get; set; }
- public long Version { get; set; }
- }
-}
diff --git a/BackEnd/Timeline/Resources/Services/Exceptions.Designer.cs b/BackEnd/Timeline/Resources/Services/Exceptions.Designer.cs
index 1dbe11c9..7f00d60d 100644
--- a/BackEnd/Timeline/Resources/Services/Exceptions.Designer.cs
+++ b/BackEnd/Timeline/Resources/Services/Exceptions.Designer.cs
@@ -160,7 +160,7 @@ namespace Timeline.Resources.Services {
}
/// <summary>
- /// Looks up a localized string similar to Request timeline name is &quot;{0}&quot;. Request timeline post id is &quot;{1}&quot;..
+ /// Looks up a localized string similar to Request timeline id is &quot;{0}&quot;. Request timeline post id is &quot;{1}&quot;..
/// </summary>
internal static string TimelinePostNotExistException {
get {
@@ -169,7 +169,7 @@ namespace Timeline.Resources.Services {
}
/// <summary>
- /// Looks up a localized string similar to Request timeline name is &quot;{0}&quot;. Request timeline post id is &quot;{1}&quot;. The post does not exist because it is deleted..
+ /// Looks up a localized string similar to Request timeline id is &quot;{0}&quot;. Request timeline post id is &quot;{1}&quot;. The post does not exist because it is deleted..
/// </summary>
internal static string TimelinePostNotExistExceptionDeleted {
get {
diff --git a/BackEnd/Timeline/Resources/Services/Exceptions.resx b/BackEnd/Timeline/Resources/Services/Exceptions.resx
index e9595caa..d988b084 100644
--- a/BackEnd/Timeline/Resources/Services/Exceptions.resx
+++ b/BackEnd/Timeline/Resources/Services/Exceptions.resx
@@ -1,76 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
- Microsoft ResX Schema
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
- Version 1.3
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">1.3</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1">this is my long string</data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- [base64 mime encoded serialized .NET Framework object]
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- [base64 mime encoded string representing a byte array form of the .NET Framework object]
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
@@ -89,13 +109,13 @@
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
- <value>1.3</value>
+ <value>2.0</value>
</resheader>
<resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="EntityNotExistError" xml:space="preserve">
<value>The required entity of type "{0}" does not exist.</value>
@@ -113,7 +133,7 @@
<value>Request timeline name is "{0}". If this is a personal timeline whose name starts with '@', it means the user does not exist and inner exception should be a UserNotExistException.</value>
</data>
<data name="TimelinePostNotExistException" xml:space="preserve">
- <value>Request timeline name is "{0}". Request timeline post id is "{1}".</value>
+ <value>Request timeline id is "{0}". Request timeline post id is "{1}".</value>
</data>
<data name="UserNotExistException" xml:space="preserve">
<value>Request username is "{0}". Request id is "{1}".</value>
@@ -137,6 +157,6 @@
<value>image's actual mime type is not the specified one</value>
</data>
<data name="TimelinePostNotExistExceptionDeleted" xml:space="preserve">
- <value>Request timeline name is "{0}". Request timeline post id is "{1}". The post does not exist because it is deleted.</value>
+ <value>Request timeline id is "{0}". Request timeline post id is "{1}". The post does not exist because it is deleted.</value>
</data>
</root> \ No newline at end of file
diff --git a/BackEnd/Timeline/Services/BasicTimelineService.cs b/BackEnd/Timeline/Services/BasicTimelineService.cs
index 0d9f64a9..be500135 100644
--- a/BackEnd/Timeline/Services/BasicTimelineService.cs
+++ b/BackEnd/Timeline/Services/BasicTimelineService.cs
@@ -16,6 +16,13 @@ namespace Timeline.Services
public interface IBasicTimelineService
{
/// <summary>
+ /// Check whether a timeline with given id exists without getting full info.
+ /// </summary>
+ /// <param name="id">The timeline id.</param>
+ /// <returns>True if exist. Otherwise false.</returns>
+ Task<bool> CheckExistence(long id);
+
+ /// <summary>
/// Get the timeline id by name.
/// </summary>
/// <param name="timelineName">Timeline name.</param>
@@ -67,6 +74,11 @@ namespace Timeline.Services
};
}
+ public async Task<bool> CheckExistence(long id)
+ {
+ return await _database.Timelines.AnyAsync(t => t.Id == id);
+ }
+
public async Task<long> GetTimelineIdByName(string timelineName)
{
if (timelineName == null)
diff --git a/BackEnd/Timeline/Services/BookmarkTimelineService.cs b/BackEnd/Timeline/Services/BookmarkTimelineService.cs
index 2ec3af62..380e1909 100644
--- a/BackEnd/Timeline/Services/BookmarkTimelineService.cs
+++ b/BackEnd/Timeline/Services/BookmarkTimelineService.cs
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
-using Timeline.Models;
using Timeline.Services.Exceptions;
namespace Timeline.Services
@@ -30,9 +29,9 @@ namespace Timeline.Services
/// Get bookmarks of a user.
/// </summary>
/// <param name="userId">User id of bookmark owner.</param>
- /// <returns>Bookmarks in order.</returns>
+ /// <returns>Id of Bookmark timelines in order.</returns>
/// <exception cref="UserNotExistException">Thrown when user does not exist.</exception>
- Task<List<TimelineInfo>> GetBookmarks(long userId);
+ Task<List<long>> GetBookmarks(long userId);
/// <summary>
/// Add a bookmark to tail to a user.
@@ -75,9 +74,9 @@ namespace Timeline.Services
{
private readonly DatabaseContext _database;
private readonly IBasicUserService _userService;
- private readonly ITimelineService _timelineService;
+ private readonly IBasicTimelineService _timelineService;
- public BookmarkTimelineService(DatabaseContext database, IBasicUserService userService, ITimelineService timelineService)
+ public BookmarkTimelineService(DatabaseContext database, IBasicUserService userService, IBasicTimelineService timelineService)
{
_database = database;
_userService = userService;
@@ -109,21 +108,14 @@ namespace Timeline.Services
await _database.SaveChangesAsync();
}
- public async Task<List<TimelineInfo>> GetBookmarks(long userId)
+ public async Task<List<long>> GetBookmarks(long userId)
{
if (!await _userService.CheckUserExistence(userId))
throw new UserNotExistException(userId);
var entities = await _database.BookmarkTimelines.Where(t => t.UserId == userId).OrderBy(t => t.Rank).Select(t => new { t.TimelineId }).ToListAsync();
- List<TimelineInfo> result = new();
-
- foreach (var entity in entities)
- {
- result.Add(await _timelineService.GetTimelineById(entity.TimelineId));
- }
-
- return result;
+ return entities.Select(e => e.TimelineId).ToList();
}
public async Task MoveBookmark(long userId, string timelineName, long newPosition)
diff --git a/BackEnd/Timeline/Services/Exceptions/TimelinePostNotExistException.cs b/BackEnd/Timeline/Services/Exceptions/TimelinePostNotExistException.cs
index f95dd410..2a7b5b28 100644
--- a/BackEnd/Timeline/Services/Exceptions/TimelinePostNotExistException.cs
+++ b/BackEnd/Timeline/Services/Exceptions/TimelinePostNotExistException.cs
@@ -14,16 +14,21 @@ namespace Timeline.Services.Exceptions
protected TimelinePostNotExistException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+ public TimelinePostNotExistException(long? timelineId, long? postId, bool isDelete, string? message = null, Exception? inner = null)
+ : base(EntityNames.TimelinePost, null, MakeMessage(timelineId, postId, isDelete).AppendAdditionalMessage(message), inner)
+ {
+ TimelineId = timelineId;
+ PostId = postId;
+ IsDelete = isDelete;
+ }
- public TimelinePostNotExistException(string? timelineName, long? id, bool isDelete, string? message = null, Exception? inner = null) : base(EntityNames.TimelinePost, null, MakeMessage(timelineName, id, isDelete).AppendAdditionalMessage(message), inner) { TimelineName = timelineName; Id = id; IsDelete = isDelete; }
-
- private static string MakeMessage(string? timelineName, long? id, bool isDelete)
+ private static string MakeMessage(long? timelineId, long? postId, bool isDelete)
{
- return string.Format(CultureInfo.InvariantCulture, isDelete ? Resources.Services.Exceptions.TimelinePostNotExistExceptionDeleted : Resources.Services.Exceptions.TimelinePostNotExistException, timelineName ?? "", id);
+ return string.Format(CultureInfo.InvariantCulture, isDelete ? Resources.Services.Exceptions.TimelinePostNotExistExceptionDeleted : Resources.Services.Exceptions.TimelinePostNotExistException, timelineId, postId);
}
- public string? TimelineName { get; set; }
- public long? Id { get; set; }
+ public long? TimelineId { get; set; }
+ public long? PostId { get; set; }
/// <summary>
/// True if the post is deleted. False if the post does not exist at all.
diff --git a/BackEnd/Timeline/Services/HighlightTimelineService.cs b/BackEnd/Timeline/Services/HighlightTimelineService.cs
index b19efe21..d0a06fe7 100644
--- a/BackEnd/Timeline/Services/HighlightTimelineService.cs
+++ b/BackEnd/Timeline/Services/HighlightTimelineService.cs
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
-using Timeline.Models;
using Timeline.Services.Exceptions;
namespace Timeline.Services
@@ -29,8 +28,8 @@ namespace Timeline.Services
/// <summary>
/// Get all highlight timelines in order.
/// </summary>
- /// <returns>A list of all highlight timelines.</returns>
- Task<List<TimelineInfo>> GetHighlightTimelines();
+ /// <returns>Id list of all highlight timelines.</returns>
+ Task<List<long>> GetHighlightTimelines();
/// <summary>
/// Add a timeline to highlight list.
@@ -75,10 +74,10 @@ namespace Timeline.Services
{
private readonly DatabaseContext _database;
private readonly IBasicUserService _userService;
- private readonly ITimelineService _timelineService;
+ private readonly IBasicTimelineService _timelineService;
private readonly IClock _clock;
- public HighlightTimelineService(DatabaseContext database, IBasicUserService userService, ITimelineService timelineService, IClock clock)
+ public HighlightTimelineService(DatabaseContext database, IBasicUserService userService, IBasicTimelineService timelineService, IClock clock)
{
_database = database;
_userService = userService;
@@ -106,18 +105,11 @@ namespace Timeline.Services
await _database.SaveChangesAsync();
}
- public async Task<List<TimelineInfo>> GetHighlightTimelines()
+ public async Task<List<long>> GetHighlightTimelines()
{
var entities = await _database.HighlightTimelines.OrderBy(t => t.Order).Select(t => new { t.TimelineId }).ToListAsync();
- var result = new List<TimelineInfo>();
-
- foreach (var entity in entities)
- {
- result.Add(await _timelineService.GetTimelineById(entity.TimelineId));
- }
-
- return result;
+ return entities.Select(e => e.TimelineId).ToList();
}
public async Task<bool> RemoveHighlightTimeline(string timelineName, long? operatorId)
diff --git a/BackEnd/Timeline/Services/TimelinePostService.cs b/BackEnd/Timeline/Services/TimelinePostService.cs
index 35513a36..9f0fd550 100644
--- a/BackEnd/Timeline/Services/TimelinePostService.cs
+++ b/BackEnd/Timeline/Services/TimelinePostService.cs
@@ -29,103 +29,74 @@ namespace Timeline.Services
/// <summary>
/// Get all the posts in the timeline.
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
+ /// <param name="timelineId">The id of the timeline.</param>
/// <param name="modifiedSince">The time that posts have been modified since.</param>
/// <param name="includeDeleted">Whether include deleted posts.</param>
/// <returns>A list of all posts.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
- Task<List<TimelinePostInfo>> GetPosts(string timelineName, DateTime? modifiedSince = null, bool includeDeleted = false);
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
+ Task<List<TimelinePostEntity>> GetPosts(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false);
/// <summary>
/// Get the etag of data of a post.
/// </summary>
- /// <param name="timelineName">The name of the timeline of the post.</param>
+ /// <param name="timelineId">The id of the timeline of the post.</param>
/// <param name="postId">The id of the post.</param>
/// <returns>The etag of the data.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <exception cref="TimelinePostNotExistException">Thrown when post of <paramref name="postId"/> does not exist or has been deleted.</exception>
/// <exception cref="TimelinePostNoDataException">Thrown when post has no data.</exception>
- /// <seealso cref="GetPostData(string, long)"/>
- Task<string> GetPostDataETag(string timelineName, long postId);
+ Task<string> GetPostDataETag(long timelineId, long postId);
/// <summary>
/// Get the data of a post.
/// </summary>
- /// <param name="timelineName">The name of the timeline of the post.</param>
+ /// <param name="timelineId">The id of the timeline of the post.</param>
/// <param name="postId">The id of the post.</param>
/// <returns>The etag of the data.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <exception cref="TimelinePostNotExistException">Thrown when post of <paramref name="postId"/> does not exist or has been deleted.</exception>
/// <exception cref="TimelinePostNoDataException">Thrown when post has no data.</exception>
- /// <seealso cref="GetPostDataETag(string, long)"/>
- Task<PostData> GetPostData(string timelineName, long postId);
+ /// <seealso cref="GetPostDataETag(long, long)"/>
+ Task<PostData> GetPostData(long timelineId, long postId);
/// <summary>
/// Create a new text post in timeline.
/// </summary>
- /// <param name="timelineName">The name of the timeline to create post against.</param>
+ /// <param name="timelineId">The id of the timeline to create post against.</param>
/// <param name="authorId">The author's user id.</param>
/// <param name="text">The content text.</param>
/// <param name="time">The time of the post. If null, then current time is used.</param>
/// <returns>The info of the created post.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> or <paramref name="text"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="text"/> is null.</exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <exception cref="UserNotExistException">Thrown if user of <paramref name="authorId"/> does not exist.</exception>
- Task<TimelinePostInfo> CreateTextPost(string timelineName, long authorId, string text, DateTime? time);
+ Task<TimelinePostEntity> CreateTextPost(long timelineId, long authorId, string text, DateTime? time);
/// <summary>
/// Create a new image post in timeline.
/// </summary>
- /// <param name="timelineName">The name of the timeline to create post against.</param>
+ /// <param name="timelineId">The id of the timeline to create post against.</param>
/// <param name="authorId">The author's user id.</param>
/// <param name="imageData">The image data.</param>
/// <param name="time">The time of the post. If null, then use current time.</param>
/// <returns>The info of the created post.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> or <paramref name="imageData"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="imageData"/> is null.</exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <exception cref="UserNotExistException">Thrown if user of <paramref name="authorId"/> does not exist.</exception>
/// <exception cref="ImageException">Thrown if data is not a image. Validated by <see cref="ImageValidator"/>.</exception>
- Task<TimelinePostInfo> CreateImagePost(string timelineName, long authorId, byte[] imageData, DateTime? time);
+ Task<TimelinePostEntity> CreateImagePost(long timelineId, long authorId, byte[] imageData, DateTime? time);
/// <summary>
/// Delete a post.
/// </summary>
- /// <param name="timelineName">The name of the timeline to delete post against.</param>
+ /// <param name="timelineId">The id of the timeline to delete post against.</param>
/// <param name="postId">The id of the post to delete.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <exception cref="TimelinePostNotExistException">Thrown when the post with given id does not exist or is deleted already.</exception>
/// <remarks>
- /// First use <see cref="HasPostModifyPermission(string, long, long, bool)"/> to check the permission.
+ /// First use <see cref="HasPostModifyPermission(long, long, long, bool)"/> to check the permission.
/// </remarks>
- Task DeletePost(string timelineName, long postId);
+ Task DeletePost(long timelineId, long postId);
/// <summary>
/// Delete all posts of the given user. Used when delete a user.
@@ -136,17 +107,12 @@ namespace Timeline.Services
/// <summary>
/// Verify whether a user has the permission to modify a post.
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
+ /// <param name="timelineId">The id of the timeline.</param>
/// <param name="postId">The id of the post.</param>
/// <param name="modifierId">The id of the user to check on.</param>
/// <param name="throwOnPostNotExist">True if you want it to throw <see cref="TimelinePostNotExistException"/>. Default false.</param>
/// <returns>True if can modify, false if can't modify.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <exception cref="TimelinePostNotExistException">Thrown when the post with given id does not exist or is deleted already and <paramref name="throwOnPostNotExist"/> is true.</exception>
/// <remarks>
/// Unless <paramref name="throwOnPostNotExist"/> is true, this method should return true if the post does not exist.
@@ -155,7 +121,7 @@ namespace Timeline.Services
/// It only checks whether he is the author of the post or the owner of the timeline.
/// Return false when user with modifier id does not exist.
/// </remarks>
- Task<bool> HasPostModifyPermission(string timelineName, long postId, long modifierId, bool throwOnPostNotExist = false);
+ Task<bool> HasPostModifyPermission(long timelineId, long postId, long modifierId, bool throwOnPostNotExist = false);
}
public class TimelinePostService : ITimelinePostService
@@ -179,42 +145,18 @@ namespace Timeline.Services
_clock = clock;
}
- private async Task<TimelinePostInfo> MapTimelinePostFromEntity(TimelinePostEntity entity, string timelineName)
+ private async Task CheckTimelineExistence(long timelineId)
{
- UserInfo? author = entity.AuthorId.HasValue ? await _userService.GetUser(entity.AuthorId.Value) : null;
-
- ITimelinePostContent? content = null;
-
- if (entity.Content != null)
- {
- var type = entity.ContentType;
-
- content = type switch
- {
- TimelinePostContentTypes.Text => new TextTimelinePostContent(entity.Content),
- TimelinePostContentTypes.Image => new ImageTimelinePostContent(entity.Content),
- _ => throw new DatabaseCorruptedException(string.Format(CultureInfo.InvariantCulture, ExceptionDatabaseUnknownContentType, type))
- };
- }
-
- return new TimelinePostInfo(
- id: entity.LocalId,
- author: author,
- content: content,
- time: entity.Time,
- lastUpdated: entity.LastUpdated,
- timelineName: timelineName
- );
+ if (!await _basicTimelineService.CheckExistence(timelineId))
+ throw new TimelineNotExistException(timelineId);
}
- public async Task<List<TimelinePostInfo>> GetPosts(string timelineName, DateTime? modifiedSince = null, bool includeDeleted = false)
+ public async Task<List<TimelinePostEntity>> GetPosts(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false)
{
- modifiedSince = modifiedSince?.MyToUtc();
+ await CheckTimelineExistence(timelineId);
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
+ modifiedSince = modifiedSince?.MyToUtc();
- var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName);
IQueryable<TimelinePostEntity> query = _database.TimelinePosts.Where(p => p.TimelineId == timelineId);
if (!includeDeleted)
@@ -229,30 +171,20 @@ namespace Timeline.Services
query = query.OrderBy(p => p.Time);
- var postEntities = await query.ToListAsync();
-
- var posts = new List<TimelinePostInfo>();
- foreach (var entity in postEntities)
- {
- posts.Add(await MapTimelinePostFromEntity(entity, timelineName));
- }
- return posts;
+ return await query.Include(p => p.Author!.Permissions).ToListAsync();
}
- public async Task<string> GetPostDataETag(string timelineName, long postId)
+ public async Task<string> GetPostDataETag(long timelineId, long postId)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
-
- var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName);
+ await CheckTimelineExistence(timelineId);
var postEntity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync();
if (postEntity == null)
- throw new TimelinePostNotExistException(timelineName, postId, false);
+ throw new TimelinePostNotExistException(timelineId, postId, false);
if (postEntity.Content == null)
- throw new TimelinePostNotExistException(timelineName, postId, true);
+ throw new TimelinePostNotExistException(timelineId, postId, true);
if (postEntity.ContentType != TimelinePostContentTypes.Image)
throw new TimelinePostNoDataException(ExceptionGetDataNonImagePost);
@@ -262,19 +194,17 @@ namespace Timeline.Services
return tag;
}
- public async Task<PostData> GetPostData(string timelineName, long postId)
+ public async Task<PostData> GetPostData(long timelineId, long postId)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
+ await CheckTimelineExistence(timelineId);
- var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName);
var postEntity = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync();
if (postEntity == null)
- throw new TimelinePostNotExistException(timelineName, postId, false);
+ throw new TimelinePostNotExistException(timelineId, postId, false);
if (postEntity.Content == null)
- throw new TimelinePostNotExistException(timelineName, postId, true);
+ throw new TimelinePostNotExistException(timelineId, postId, true);
if (postEntity.ContentType != TimelinePostContentTypes.Image)
throw new TimelinePostNoDataException(ExceptionGetDataNonImagePost);
@@ -309,16 +239,15 @@ namespace Timeline.Services
};
}
- public async Task<TimelinePostInfo> CreateTextPost(string timelineName, long authorId, string text, DateTime? time)
+ public async Task<TimelinePostEntity> CreateTextPost(long timelineId, long authorId, string text, DateTime? time)
{
- time = time?.MyToUtc();
-
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
- if (text == null)
+ if (text is null)
throw new ArgumentNullException(nameof(text));
- var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName);
+ await CheckTimelineExistence(timelineId);
+
+ time = time?.MyToUtc();
+
var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
var author = await _userService.GetUser(authorId);
@@ -341,27 +270,20 @@ namespace Timeline.Services
_database.TimelinePosts.Add(postEntity);
await _database.SaveChangesAsync();
+ await _database.Entry(postEntity).Reference(p => p.Author).Query().Include(a => a.Permissions).LoadAsync();
- return new TimelinePostInfo(
- id: postEntity.LocalId,
- content: new TextTimelinePostContent(text),
- time: finalTime,
- author: author,
- lastUpdated: currentTime,
- timelineName: timelineName
- );
+ return postEntity;
}
- public async Task<TimelinePostInfo> CreateImagePost(string timelineName, long authorId, byte[] data, DateTime? time)
+ public async Task<TimelinePostEntity> CreateImagePost(long timelineId, long authorId, byte[] data, DateTime? time)
{
- time = time?.MyToUtc();
-
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
- if (data == null)
+ if (data is null)
throw new ArgumentNullException(nameof(data));
- var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName);
+ await CheckTimelineExistence(timelineId);
+
+ time = time?.MyToUtc();
+
var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
var author = await _userService.GetUser(authorId);
@@ -391,30 +313,22 @@ namespace Timeline.Services
_database.TimelinePosts.Add(postEntity);
await _database.SaveChangesAsync();
- return new TimelinePostInfo(
- id: postEntity.LocalId,
- content: new ImageTimelinePostContent(tag),
- time: finalTime,
- author: author,
- lastUpdated: currentTime,
- timelineName: timelineName
- );
+ await _database.Entry(postEntity).Reference(p => p.Author).Query().Include(a => a.Permissions).LoadAsync();
+
+ return postEntity;
}
- public async Task DeletePost(string timelineName, long id)
+ public async Task DeletePost(long timelineId, long postId)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
-
- var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName);
+ await CheckTimelineExistence(timelineId);
- var post = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == id).SingleOrDefaultAsync();
+ var post = await _database.TimelinePosts.Where(p => p.TimelineId == timelineId && p.LocalId == postId).SingleOrDefaultAsync();
if (post == null)
- throw new TimelinePostNotExistException(timelineName, id, false);
+ throw new TimelinePostNotExistException(timelineId, postId, false);
if (post.Content == null)
- throw new TimelinePostNotExistException(timelineName, id, true);
+ throw new TimelinePostNotExistException(timelineId, postId, true);
string? dataTag = null;
@@ -463,12 +377,9 @@ namespace Timeline.Services
}
}
- public async Task<bool> HasPostModifyPermission(string timelineName, long postId, long modifierId, bool throwOnPostNotExist = false)
+ public async Task<bool> HasPostModifyPermission(long timelineId, long postId, long modifierId, bool throwOnPostNotExist = false)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
-
- var timelineId = await _basicTimelineService.GetTimelineIdByName(timelineName);
+ await CheckTimelineExistence(timelineId);
var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync();
@@ -477,14 +388,14 @@ namespace Timeline.Services
if (postEntity == null)
{
if (throwOnPostNotExist)
- throw new TimelinePostNotExistException(timelineName, postId, false);
+ throw new TimelinePostNotExistException(timelineId, postId, false);
else
return true;
}
if (postEntity.Content == null && throwOnPostNotExist)
{
- throw new TimelinePostNotExistException(timelineName, postId, true);
+ throw new TimelinePostNotExistException(timelineId, postId, true);
}
return timelineEntity.OwnerId == modifierId || postEntity.AuthorId == modifierId;
diff --git a/BackEnd/Timeline/Services/TimelineService.cs b/BackEnd/Timeline/Services/TimelineService.cs
index b65b3cf4..e310951a 100644
--- a/BackEnd/Timeline/Services/TimelineService.cs
+++ b/BackEnd/Timeline/Services/TimelineService.cs
@@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
@@ -19,7 +18,7 @@ namespace Timeline.Services
if (name.StartsWith("@", StringComparison.OrdinalIgnoreCase))
{
isPersonal = true;
- return name.Substring(1);
+ return name[1..];
}
else
{
@@ -48,6 +47,13 @@ namespace Timeline.Services
public long UserId { get; set; }
}
+ public class TimelineChangePropertyParams
+ {
+ public string? Title { get; set; }
+ public string? Description { get; set; }
+ public TimelineVisibility? Visibility { get; set; }
+ }
+
/// <summary>
/// This define the interface of both personal timeline and ordinary timeline.
/// </summary>
@@ -56,139 +62,94 @@ namespace Timeline.Services
/// <summary>
/// Get the timeline last modified time (not include name change).
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
- /// <returns>The timeline info.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
- Task<DateTime> GetTimelineLastModifiedTime(string timelineName);
+ /// <param name="id">The id of the timeline.</param>
+ /// <returns>The timeline modified time.</returns>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
+ Task<DateTime> GetTimelineLastModifiedTime(long id);
/// <summary>
/// Get the timeline unique id.
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
- /// <returns>The timeline info.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
- Task<string> GetTimelineUniqueId(string timelineName);
+ /// <param name="id">The id of the timeline.</param>
+ /// <returns>The timeline unique id.</returns>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
+ Task<string> GetTimelineUniqueId(long id);
/// <summary>
/// Get the timeline info.
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
+ /// <param name="id">Id of timeline.</param>
/// <returns>The timeline info.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
- Task<TimelineInfo> GetTimeline(string timelineName);
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
+ Task<TimelineEntity> GetTimeline(long id);
/// <summary>
- /// Get timeline by id.
+ /// Set the properties of a timeline.
/// </summary>
- /// <param name="id">Id of timeline.</param>
- /// <returns>The timeline.</returns>
+ /// <param name="id">The id of the timeline.</param>
+ /// <param name="newProperties">The new properties. Null member means not to change.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="newProperties"/> is null.</exception>
/// <exception cref="TimelineNotExistException">Thrown when timeline with given id does not exist.</exception>
- Task<TimelineInfo> GetTimelineById(long id);
+ Task ChangeProperty(long id, TimelineChangePropertyParams newProperties);
/// <summary>
- /// Set the properties of a timeline.
+ /// Add a member to timeline.
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
- /// <param name="newProperties">The new properties. Null member means not to change.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> or <paramref name="newProperties"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
- Task ChangeProperty(string timelineName, TimelineChangePropertyRequest newProperties);
+ /// <param name="timelineId">Timeline id.</param>
+ /// <param name="userId">User id.</param>
+ /// <returns>True if the memeber was added. False if it is already a member.</returns>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user does not exist.</exception>
+ Task<bool> AddMember(long timelineId, long userId);
/// <summary>
- /// Change member of timeline.
+ /// Remove a member from timeline.
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
- /// <param name="membersToAdd">A list of usernames of members to add. May be null.</param>
- /// <param name="membersToRemove">A list of usernames of members to remove. May be null.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
- /// <exception cref="ArgumentException">Thrown when names in <paramref name="membersToAdd"/> or <paramref name="membersToRemove"/> is not a valid username.</exception>
- /// <exception cref="UserNotExistException">Thrown when one of the user to change does not exist.</exception>
- /// <remarks>
- /// Operating on a username that is of bad format or does not exist always throws.
- /// Add a user that already is a member has no effects.
- /// Remove a user that is not a member also has not effects.
- /// Add and remove an identical user results in no effects.
- /// More than one same usernames are regarded as one.
- /// </remarks>
- Task ChangeMember(string timelineName, IList<string>? membersToAdd, IList<string>? membersToRemove);
+ /// <param name="timelineId">Timeline id.</param>
+ /// <param name="userId">User id.</param>
+ /// <returns>True if the memeber was removed. False if it was not a member before.</returns>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
+ /// <exception cref="UserNotExistException">Thrown when the user does not exist.</exception>
+ Task<bool> RemoveMember(long timelineId, long userId);
/// <summary>
/// Check whether a user can manage(change timeline info, member, ...) a timeline.
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
+ /// <param name="timelineId">The id of the timeline.</param>
/// <param name="userId">The id of the user to check on.</param>
/// <returns>True if the user can manage the timeline, otherwise false.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <remarks>
/// This method does not check whether visitor is administrator.
/// Return false if user with user id does not exist.
/// </remarks>
- Task<bool> HasManagePermission(string timelineName, long userId);
+ Task<bool> HasManagePermission(long timelineId, long userId);
/// <summary>
/// Verify whether a visitor has the permission to read a timeline.
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
+ /// <param name="timelineId">The id of the timeline.</param>
/// <param name="visitorId">The id of the user to check on. Null means visitor without account.</param>
/// <returns>True if can read, false if can't read.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <remarks>
/// This method does not check whether visitor is administrator.
/// Return false if user with visitor id does not exist.
/// </remarks>
- Task<bool> HasReadPermission(string timelineName, long? visitorId);
+ Task<bool> HasReadPermission(long timelineId, long? visitorId);
/// <summary>
/// Verify whether a user is member of a timeline.
/// </summary>
- /// <param name="timelineName">The name of the timeline.</param>
+ /// <param name="timelineId">The id of the timeline.</param>
/// <param name="userId">The id of user to check on.</param>
/// <returns>True if it is a member, false if not.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Throw when <paramref name="timelineName"/> is of bad format.</exception>
- /// <exception cref="TimelineNotExistException">
- /// Thrown when timeline with name <paramref name="timelineName"/> does not exist.
- /// If it is a personal timeline, then inner exception is <see cref="UserNotExistException"/>.
- /// </exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <remarks>
/// Timeline owner is also considered as a member.
/// Return false when user with user id does not exist.
/// </remarks>
- Task<bool> IsMemberOf(string timelineName, long userId);
+ Task<bool> IsMemberOf(long timelineId, long userId);
/// <summary>
/// Get all timelines including personal and ordinary timelines.
@@ -199,7 +160,7 @@ namespace Timeline.Services
/// <remarks>
/// If user with related user id does not exist, empty list will be returned.
/// </remarks>
- Task<List<TimelineInfo>> GetTimelines(TimelineUserRelationship? relate = null, List<TimelineVisibility>? visibility = null);
+ Task<List<TimelineEntity>> GetTimelines(TimelineUserRelationship? relate = null, List<TimelineVisibility>? visibility = null);
/// <summary>
/// Create a timeline.
@@ -211,36 +172,33 @@ namespace Timeline.Services
/// <exception cref="ArgumentException">Thrown when timeline name is invalid.</exception>
/// <exception cref="EntityAlreadyExistException">Thrown when the timeline already exists.</exception>
/// <exception cref="UserNotExistException">Thrown when the owner user does not exist.</exception>
- Task<TimelineInfo> CreateTimeline(string timelineName, long ownerId);
+ Task<TimelineEntity> CreateTimeline(string timelineName, long ownerId);
/// <summary>
/// Delete a timeline.
/// </summary>
- /// <param name="timelineName">The name of the timeline to delete.</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="timelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when timeline name is invalid.</exception>
+ /// <param name="id">The id of the timeline to delete.</param>
/// <exception cref="TimelineNotExistException">Thrown when the timeline does not exist.</exception>
- Task DeleteTimeline(string timelineName);
+ Task DeleteTimeline(long id);
/// <summary>
/// Change name of a timeline.
/// </summary>
- /// <param name="oldTimelineName">The old timeline name.</param>
+ /// <param name="id">The timeline id.</param>
/// <param name="newTimelineName">The new timeline name.</param>
- /// <returns>The new timeline info.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="oldTimelineName"/> or <paramref name="newTimelineName"/> is null.</exception>
- /// <exception cref="ArgumentException">Thrown when <paramref name="oldTimelineName"/> or <paramref name="newTimelineName"/> is of invalid format.</exception>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="newTimelineName"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="newTimelineName"/> is of invalid format.</exception>
/// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <exception cref="EntityAlreadyExistException">Thrown when a timeline with new name already exists.</exception>
/// <remarks>
/// You can only change name of general timeline.
/// </remarks>
- Task<TimelineInfo> ChangeTimelineName(string oldTimelineName, string newTimelineName);
+ Task ChangeTimelineName(long id, string newTimelineName);
}
public class TimelineService : BasicTimelineService, ITimelineService
{
- public TimelineService(DatabaseContext database, IUserService userService, IClock clock)
+ public TimelineService(DatabaseContext database, IBasicUserService userService, IClock clock)
: base(database, userService, clock)
{
_database = database;
@@ -250,12 +208,10 @@ namespace Timeline.Services
private readonly DatabaseContext _database;
- private readonly IUserService _userService;
+ private readonly IBasicUserService _userService;
private readonly IClock _clock;
- private readonly UsernameValidator _usernameValidator = new UsernameValidator();
-
private readonly TimelineNameValidator _timelineNameValidator = new TimelineNameValidator();
private void ValidateTimelineName(string name, string paramName)
@@ -266,230 +222,138 @@ namespace Timeline.Services
}
}
- /// Remember to include Members when query.
- private async Task<TimelineInfo> MapTimelineFromEntity(TimelineEntity entity)
+ public async Task<DateTime> GetTimelineLastModifiedTime(long id)
{
- var owner = await _userService.GetUser(entity.OwnerId);
+ var entity = await _database.Timelines.Where(t => t.Id == id).Select(t => new { t.LastModified }).SingleOrDefaultAsync();
- var members = new List<UserInfo>();
- foreach (var memberEntity in entity.Members)
- {
- members.Add(await _userService.GetUser(memberEntity.UserId));
- }
-
- var name = entity.Name ?? ("@" + owner.Username);
-
- return new TimelineInfo(
- entity.UniqueId,
- name,
- entity.NameLastModified,
- string.IsNullOrEmpty(entity.Title) ? name : entity.Title,
- entity.Description ?? "",
- owner,
- entity.Visibility,
- members,
- entity.CreateTime,
- entity.LastModified
- );
- }
-
- public async Task<DateTime> GetTimelineLastModifiedTime(string timelineName)
- {
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
-
- var timelineId = await GetTimelineIdByName(timelineName);
-
- var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.LastModified }).SingleAsync();
-
- return timelineEntity.LastModified;
- }
-
- public async Task<string> GetTimelineUniqueId(string timelineName)
- {
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
-
- var timelineId = await GetTimelineIdByName(timelineName);
-
- var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.UniqueId }).SingleAsync();
+ if (entity is null)
+ throw new TimelineNotExistException(id);
- return timelineEntity.UniqueId;
+ return entity.LastModified;
}
- public async Task<TimelineInfo> GetTimeline(string timelineName)
+ public async Task<string> GetTimelineUniqueId(long id)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
-
- var timelineId = await GetTimelineIdByName(timelineName);
+ var entity = await _database.Timelines.Where(t => t.Id == id).Select(t => new { t.UniqueId }).SingleOrDefaultAsync();
- var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Include(t => t.Members).SingleAsync();
+ if (entity is null)
+ throw new TimelineNotExistException(id);
- return await MapTimelineFromEntity(timelineEntity);
+ return entity.UniqueId;
}
- public async Task<TimelineInfo> GetTimelineById(long id)
+ public async Task<TimelineEntity> GetTimeline(long id)
{
- var timelineEntity = await _database.Timelines.Where(t => t.Id == id).Include(t => t.Members).SingleOrDefaultAsync();
+ var entity = await _database.Timelines.Where(t => t.Id == id).Include(t => t.Owner).ThenInclude(o => o.Permissions).Include(t => t.Members).ThenInclude(m => m.User).ThenInclude(u => u.Permissions).SingleOrDefaultAsync();
- if (timelineEntity is null)
+ if (entity is null)
throw new TimelineNotExistException(id);
- return await MapTimelineFromEntity(timelineEntity);
+ return entity;
}
- public async Task ChangeProperty(string timelineName, TimelineChangePropertyRequest newProperties)
+ public async Task ChangeProperty(long id, TimelineChangePropertyParams newProperties)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
- if (newProperties == null)
+ if (newProperties is null)
throw new ArgumentNullException(nameof(newProperties));
- var timelineId = await GetTimelineIdByName(timelineName);
+ var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync();
- var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
+ if (entity is null)
+ throw new TimelineNotExistException(id);
var changed = false;
if (newProperties.Title != null)
{
changed = true;
- timelineEntity.Title = newProperties.Title;
+ entity.Title = newProperties.Title;
}
if (newProperties.Description != null)
{
changed = true;
- timelineEntity.Description = newProperties.Description;
+ entity.Description = newProperties.Description;
}
if (newProperties.Visibility.HasValue)
{
changed = true;
- timelineEntity.Visibility = newProperties.Visibility.Value;
+ entity.Visibility = newProperties.Visibility.Value;
}
if (changed)
{
var currentTime = _clock.GetCurrentTime();
- timelineEntity.LastModified = currentTime;
+ entity.LastModified = currentTime;
}
await _database.SaveChangesAsync();
}
- public async Task ChangeMember(string timelineName, IList<string>? add, IList<string>? remove)
+ public async Task<bool> AddMember(long timelineId, long userId)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
+ if (!await CheckExistence(timelineId))
+ throw new TimelineNotExistException(timelineId);
- List<string>? RemoveDuplicateAndCheckFormat(IList<string>? list, string paramName)
- {
- if (list != null)
- {
- List<string> result = new List<string>();
- var count = list.Count;
- for (var index = 0; index < count; index++)
- {
- var username = list[index];
- if (result.Contains(username))
- {
- continue;
- }
- var (validationResult, message) = _usernameValidator.Validate(username);
- if (!validationResult)
- throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionChangeMemberUsernameBadFormat, index), nameof(paramName));
- result.Add(username);
- }
- return result;
- }
- else
- {
- return null;
- }
- }
- var simplifiedAdd = RemoveDuplicateAndCheckFormat(add, nameof(add));
- var simplifiedRemove = RemoveDuplicateAndCheckFormat(remove, nameof(remove));
+ if (!await _userService.CheckUserExistence(userId))
+ throw new UserNotExistException(userId);
- // remove those both in add and remove
- if (simplifiedAdd != null && simplifiedRemove != null)
- {
- var usersToClean = simplifiedRemove.Where(u => simplifiedAdd.Contains(u)).ToList();
- foreach (var u in usersToClean)
- {
- simplifiedAdd.Remove(u);
- simplifiedRemove.Remove(u);
- }
+ if (await _database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId))
+ return false;
- if (simplifiedAdd.Count == 0)
- simplifiedAdd = null;
- if (simplifiedRemove.Count == 0)
- simplifiedRemove = null;
- }
+ var entity = new TimelineMemberEntity { UserId = userId, TimelineId = timelineId };
+ _database.TimelineMembers.Add(entity);
- if (simplifiedAdd == null && simplifiedRemove == null)
- return;
+ var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
+ timelineEntity.LastModified = _clock.GetCurrentTime();
- var timelineId = await GetTimelineIdByName(timelineName);
+ await _database.SaveChangesAsync();
+ return true;
+ }
- async Task<List<long>?> CheckExistenceAndGetId(List<string>? list)
- {
- if (list == null)
- return null;
+ public async Task<bool> RemoveMember(long timelineId, long userId)
+ {
+ if (!await CheckExistence(timelineId))
+ throw new TimelineNotExistException(timelineId);
- List<long> result = new List<long>();
- foreach (var username in list)
- {
- result.Add(await _userService.GetUserIdByUsername(username));
- }
- return result;
- }
- var userIdsAdd = await CheckExistenceAndGetId(simplifiedAdd);
- var userIdsRemove = await CheckExistenceAndGetId(simplifiedRemove);
+ if (!await _userService.CheckUserExistence(userId))
+ throw new UserNotExistException(userId);
- if (userIdsAdd != null)
- {
- var membersToAdd = userIdsAdd.Select(id => new TimelineMemberEntity { UserId = id, TimelineId = timelineId }).ToList();
- _database.TimelineMembers.AddRange(membersToAdd);
- }
+ var entity = await _database.TimelineMembers.SingleOrDefaultAsync(m => m.TimelineId == timelineId && m.UserId == userId);
+ if (entity is null) return false;
- if (userIdsRemove != null)
- {
- var membersToRemove = await _database.TimelineMembers.Where(m => m.TimelineId == timelineId && userIdsRemove.Contains(m.UserId)).ToListAsync();
- _database.TimelineMembers.RemoveRange(membersToRemove);
- }
+ _database.TimelineMembers.Remove(entity);
var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
timelineEntity.LastModified = _clock.GetCurrentTime();
await _database.SaveChangesAsync();
+ return true;
}
- public async Task<bool> HasManagePermission(string timelineName, long userId)
+ public async Task<bool> HasManagePermission(long timelineId, long userId)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
+ var entity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleOrDefaultAsync();
- var timelineId = await GetTimelineIdByName(timelineName);
- var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync();
+ if (entity is null)
+ throw new TimelineNotExistException(timelineId);
- return userId == timelineEntity.OwnerId;
+ return entity.OwnerId == userId;
}
- public async Task<bool> HasReadPermission(string timelineName, long? visitorId)
+ public async Task<bool> HasReadPermission(long timelineId, long? visitorId)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
+ var entity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.Visibility }).SingleOrDefaultAsync();
- var timelineId = await GetTimelineIdByName(timelineName);
- var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.Visibility }).SingleAsync();
+ if (entity is null)
+ throw new TimelineNotExistException(timelineId);
- if (timelineEntity.Visibility == TimelineVisibility.Public)
+ if (entity.Visibility == TimelineVisibility.Public)
return true;
- if (timelineEntity.Visibility == TimelineVisibility.Register && visitorId != null)
+ if (entity.Visibility == TimelineVisibility.Register && visitorId != null)
return true;
if (visitorId == null)
@@ -499,26 +363,24 @@ namespace Timeline.Services
else
{
var memberEntity = await _database.TimelineMembers.Where(m => m.UserId == visitorId && m.TimelineId == timelineId).SingleOrDefaultAsync();
- return memberEntity != null;
+ return memberEntity is not null;
}
}
- public async Task<bool> IsMemberOf(string timelineName, long userId)
+ public async Task<bool> IsMemberOf(long timelineId, long userId)
{
- if (timelineName == null)
- throw new ArgumentNullException(nameof(timelineName));
-
- var timelineId = await GetTimelineIdByName(timelineName);
+ var entity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleOrDefaultAsync();
- var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.OwnerId }).SingleAsync();
+ if (entity is null)
+ throw new TimelineNotExistException(timelineId);
- if (userId == timelineEntity.OwnerId)
+ if (userId == entity.OwnerId)
return true;
return await _database.TimelineMembers.AnyAsync(m => m.TimelineId == timelineId && m.UserId == userId);
}
- public async Task<List<TimelineInfo>> GetTimelines(TimelineUserRelationship? relate = null, List<TimelineVisibility>? visibility = null)
+ public async Task<List<TimelineEntity>> GetTimelines(TimelineUserRelationship? relate = null, List<TimelineVisibility>? visibility = null)
{
List<TimelineEntity> entities;
@@ -535,7 +397,7 @@ namespace Timeline.Services
if (relate == null)
{
- entities = await ApplyTimelineVisibilityFilter(_database.Timelines).Include(t => t.Members).ToListAsync();
+ entities = await ApplyTimelineVisibilityFilter(_database.Timelines).Include(t => t.Owner).ThenInclude(o => o.Permissions).Include(t => t.Members).ThenInclude(m => m.User).ThenInclude(u => u.Permissions).ToListAsync();
}
else
{
@@ -543,80 +405,66 @@ namespace Timeline.Services
if ((relate.Type & TimelineUserRelationshipType.Own) != 0)
{
- entities.AddRange(await ApplyTimelineVisibilityFilter(_database.Timelines.Where(t => t.OwnerId == relate.UserId)).Include(t => t.Members).ToListAsync());
+ entities.AddRange(await ApplyTimelineVisibilityFilter(_database.Timelines.Where(t => t.OwnerId == relate.UserId)).Include(t => t.Owner).ThenInclude(o => o.Permissions).Include(t => t.Members).ThenInclude(m => m.User).ThenInclude(u => u.Permissions).ToListAsync());
}
if ((relate.Type & TimelineUserRelationshipType.Join) != 0)
{
- entities.AddRange(await ApplyTimelineVisibilityFilter(_database.TimelineMembers.Where(m => m.UserId == relate.UserId).Include(m => m.Timeline).ThenInclude(t => t.Members).Select(m => m.Timeline)).ToListAsync());
+ entities.AddRange(await ApplyTimelineVisibilityFilter(_database.TimelineMembers.Where(m => m.UserId == relate.UserId).Include(m => m.Timeline).ThenInclude(t => t.Members).ThenInclude(m => m.User).ThenInclude(u => u.Permissions).Include(t => t.Timeline.Owner.Permissions).Select(m => m.Timeline)).ToListAsync());
}
}
- var result = new List<TimelineInfo>();
- foreach (var entity in entities)
- {
- result.Add(await MapTimelineFromEntity(entity));
- }
-
- return result;
+ return entities;
}
- public async Task<TimelineInfo> CreateTimeline(string name, long owner)
+ public async Task<TimelineEntity> CreateTimeline(string name, long owner)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
ValidateTimelineName(name, nameof(name));
- var user = await _userService.GetUser(owner);
-
var conflict = await _database.Timelines.AnyAsync(t => t.Name == name);
if (conflict)
throw new EntityAlreadyExistException(EntityNames.Timeline, null, ExceptionTimelineNameConflict);
- var newEntity = CreateNewTimelineEntity(name, user.Id);
+ var entity = CreateNewTimelineEntity(name, owner);
- _database.Timelines.Add(newEntity);
+ _database.Timelines.Add(entity);
await _database.SaveChangesAsync();
- return await MapTimelineFromEntity(newEntity);
+ await _database.Entry(entity).Reference(e => e.Owner).Query().Include(o => o.Permissions).LoadAsync();
+ await _database.Entry(entity).Collection(e => e.Members).Query().Include(m => m.User).ThenInclude(u => u.Permissions).LoadAsync();
+
+ return entity;
}
- public async Task DeleteTimeline(string name)
+ public async Task DeleteTimeline(long id)
{
- if (name == null)
- throw new ArgumentNullException(nameof(name));
-
- ValidateTimelineName(name, nameof(name));
+ var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync();
- var entity = await _database.Timelines.Where(t => t.Name == name).SingleOrDefaultAsync();
-
- if (entity == null)
- throw new TimelineNotExistException(name);
+ if (entity is null)
+ throw new TimelineNotExistException(id);
_database.Timelines.Remove(entity);
await _database.SaveChangesAsync();
}
- public async Task<TimelineInfo> ChangeTimelineName(string oldTimelineName, string newTimelineName)
+ public async Task ChangeTimelineName(long id, string newTimelineName)
{
- if (oldTimelineName == null)
- throw new ArgumentNullException(nameof(oldTimelineName));
if (newTimelineName == null)
throw new ArgumentNullException(nameof(newTimelineName));
- ValidateTimelineName(oldTimelineName, nameof(oldTimelineName));
ValidateTimelineName(newTimelineName, nameof(newTimelineName));
- var entity = await _database.Timelines.Include(t => t.Members).Where(t => t.Name == oldTimelineName).SingleOrDefaultAsync();
+ var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync();
- if (entity == null)
- throw new TimelineNotExistException(oldTimelineName);
+ if (entity is null)
+ throw new TimelineNotExistException(id);
- if (oldTimelineName == newTimelineName)
- return await MapTimelineFromEntity(entity);
+ if (entity.Name == newTimelineName) return;
var conflict = await _database.Timelines.AnyAsync(t => t.Name == newTimelineName);
@@ -630,8 +478,19 @@ namespace Timeline.Services
entity.LastModified = now;
await _database.SaveChangesAsync();
+ }
+ }
- return await MapTimelineFromEntity(entity);
+ public static class TimelineServiceExtensions
+ {
+ public static async Task<List<TimelineEntity>> GetTimelineList(this ITimelineService service, IEnumerable<long> ids)
+ {
+ var timelines = new List<TimelineEntity>();
+ foreach (var id in ids)
+ {
+ timelines.Add(await service.GetTimeline(id));
+ }
+ return timelines;
}
}
}
diff --git a/BackEnd/Timeline/Services/UserService.cs b/BackEnd/Timeline/Services/UserService.cs
index c99e86b0..d341759c 100644
--- a/BackEnd/Timeline/Services/UserService.cs
+++ b/BackEnd/Timeline/Services/UserService.cs
@@ -7,7 +7,6 @@ using System.Linq;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Helpers;
-using Timeline.Models;
using Timeline.Models.Validation;
using Timeline.Services.Exceptions;
using static Timeline.Resources.Services.UserService;
@@ -32,13 +31,13 @@ namespace Timeline.Services
/// <param name="id">The id of the user.</param>
/// <returns>The user info.</returns>
/// <exception cref="UserNotExistException">Thrown when the user with given id does not exist.</exception>
- Task<UserInfo> GetUser(long id);
+ Task<UserEntity> GetUser(long id);
/// <summary>
/// List all users.
/// </summary>
/// <returns>The user info of users.</returns>
- Task<List<UserInfo>> GetUsers();
+ Task<List<UserEntity>> GetUsers();
/// <summary>
/// Create a user with given info.
@@ -49,7 +48,7 @@ namespace Timeline.Services
/// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when <paramref name="username"/> or <paramref name="password"/> is of bad format.</exception>
/// <exception cref="EntityAlreadyExistException">Thrown when a user with given username already exists.</exception>
- Task<UserInfo> CreateUser(string username, string password);
+ Task<UserEntity> CreateUser(string username, string password);
/// <summary>
/// Modify a user.
@@ -62,7 +61,7 @@ namespace Timeline.Services
/// <remarks>
/// Version will increase if password is changed.
/// </remarks>
- Task<UserInfo> ModifyUser(long id, ModifyUserParams? param);
+ Task<UserEntity> ModifyUser(long id, ModifyUserParams? param);
}
public class UserService : BasicUserService, IUserService
@@ -73,17 +72,15 @@ namespace Timeline.Services
private readonly DatabaseContext _databaseContext;
private readonly IPasswordService _passwordService;
- private readonly IUserPermissionService _userPermissionService;
private readonly UsernameValidator _usernameValidator = new UsernameValidator();
private readonly NicknameValidator _nicknameValidator = new NicknameValidator();
- public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IPasswordService passwordService, IUserPermissionService userPermissionService, IClock clock) : base(databaseContext)
+ public UserService(ILogger<UserService> logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock) : base(databaseContext)
{
_logger = logger;
_databaseContext = databaseContext;
_passwordService = passwordService;
- _userPermissionService = userPermissionService;
_clock = clock;
}
@@ -116,43 +113,22 @@ namespace Timeline.Services
throw new EntityAlreadyExistException(EntityNames.User, ExceptionUsernameConflict);
}
- private async Task<UserInfo> CreateUserFromEntity(UserEntity entity)
+ public async Task<UserEntity> GetUser(long id)
{
- var permission = await _userPermissionService.GetPermissionsOfUserAsync(entity.Id);
- return new UserInfo(
- entity.Id,
- entity.UniqueId,
- entity.Username,
- string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname,
- permission,
- entity.UsernameChangeTime,
- entity.CreateTime,
- entity.LastModified,
- entity.Version
- );
- }
-
- public async Task<UserInfo> GetUser(long id)
- {
- var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
+ var user = await _databaseContext.Users.Where(u => u.Id == id).Include(u => u.Permissions).SingleOrDefaultAsync();
if (user == null)
throw new UserNotExistException(id);
- return await CreateUserFromEntity(user);
+ return user;
}
- public async Task<List<UserInfo>> GetUsers()
+ public async Task<List<UserEntity>> GetUsers()
{
- List<UserInfo> result = new();
- foreach (var entity in await _databaseContext.Users.ToArrayAsync())
- {
- result.Add(await CreateUserFromEntity(entity));
- }
- return result;
+ return await _databaseContext.Users.Include(u => u.Permissions).ToListAsync();
}
- public async Task<UserInfo> CreateUser(string username, string password)
+ public async Task<UserEntity> CreateUser(string username, string password)
{
if (username == null)
throw new ArgumentNullException(nameof(username));
@@ -177,10 +153,12 @@ namespace Timeline.Services
_logger.LogInformation(Log.Format(LogDatabaseCreate, ("Id", newEntity.Id), ("Username", username)));
- return await CreateUserFromEntity(newEntity);
+ await _databaseContext.Entry(newEntity).Collection(e => e.Permissions).LoadAsync();
+
+ return newEntity;
}
- public async Task<UserInfo> ModifyUser(long id, ModifyUserParams? param)
+ public async Task<UserEntity> ModifyUser(long id, ModifyUserParams? param)
{
if (param != null)
{
@@ -194,7 +172,7 @@ namespace Timeline.Services
CheckNicknameFormat(param.Nickname, nameof(param));
}
- var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync();
+ var entity = await _databaseContext.Users.Where(u => u.Id == id).Include(u => u.Permissions).SingleOrDefaultAsync();
if (entity == null)
throw new UserNotExistException(id);
@@ -238,7 +216,7 @@ namespace Timeline.Services
_logger.LogInformation(LogDatabaseUpdate, ("Id", id));
}
- return await CreateUserFromEntity(entity);
+ return entity;
}
}
}
diff --git a/BackEnd/Timeline/Services/UserTokenManager.cs b/BackEnd/Timeline/Services/UserTokenManager.cs
index b887b987..4e24c922 100644
--- a/BackEnd/Timeline/Services/UserTokenManager.cs
+++ b/BackEnd/Timeline/Services/UserTokenManager.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
+using Timeline.Entities;
using Timeline.Helpers;
using Timeline.Models;
using Timeline.Services.Exceptions;
@@ -10,7 +11,7 @@ namespace Timeline.Services
public class UserTokenCreateResult
{
public string Token { get; set; } = default!;
- public UserInfo User { get; set; } = default!;
+ public UserEntity User { get; set; } = default!;
}
public interface IUserTokenManager
@@ -38,7 +39,7 @@ namespace Timeline.Services
/// <exception cref="UserTokenBadVersionException">Thrown when the token is of bad version.</exception>
/// <exception cref="UserTokenBadFormatException">Thrown when the token is of bad format.</exception>
/// <exception cref="UserNotExistException">Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued.</exception>
- public Task<UserInfo> VerifyToken(string token);
+ public Task<UserEntity> VerifyToken(string token);
}
public class UserTokenManager : IUserTokenManager
@@ -75,7 +76,7 @@ namespace Timeline.Services
}
- public async Task<UserInfo> VerifyToken(string token)
+ public async Task<UserEntity> VerifyToken(string token)
{
if (token == null)
throw new ArgumentNullException(nameof(token));