From 7f88e120aba0f218641084aca4f467ffd27981d9 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 31 Jan 2021 15:42:52 +0800 Subject: ... --- BackEnd/Timeline/Controllers/TimelineController.cs | 278 ++------------------- .../Timeline/Controllers/TimelinePostController.cs | 213 ++++++++++++++++ BackEnd/Timeline/Models/Http/TimelineController.cs | 25 +- BackEnd/Timeline/Models/Mapper/TimelineMapper.cs | 4 +- BackEnd/Timeline/Properties/launchSettings.json | 14 +- BackEnd/Timeline/Services/TimelineService.cs | 99 ++------ 6 files changed, 275 insertions(+), 358 deletions(-) create mode 100644 BackEnd/Timeline/Controllers/TimelinePostController.cs (limited to 'BackEnd/Timeline') diff --git a/BackEnd/Timeline/Controllers/TimelineController.cs b/BackEnd/Timeline/Controllers/TimelineController.cs index 5d484388..06ab8004 100644 --- a/BackEnd/Timeline/Controllers/TimelineController.cs +++ b/BackEnd/Timeline/Controllers/TimelineController.cs @@ -6,9 +6,7 @@ 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; @@ -22,13 +20,13 @@ namespace Timeline.Controllers /// Operations about timeline. /// [ApiController] + [Route("timelines")] [CatchTimelineNotExistException] [ProducesErrorResponseType(typeof(CommonResponse))] public class TimelineController : Controller { private readonly IUserService _userService; private readonly ITimelineService _service; - private readonly ITimelinePostService _postService; private readonly TimelineMapper _timelineMapper; private readonly IMapper _mapper; @@ -36,11 +34,10 @@ namespace Timeline.Controllers /// /// /// - public TimelineController(IUserService userService, ITimelineService service, ITimelinePostService timelinePostService, TimelineMapper timelineMapper, IMapper mapper) + public TimelineController(IUserService userService, ITimelineService service, TimelineMapper timelineMapper, IMapper mapper) { _userService = userService; _service = service; - _postService = timelinePostService; _timelineMapper = timelineMapper; _mapper = mapper; } @@ -54,7 +51,7 @@ namespace Timeline.Controllers /// Specify the relation type, may be 'own' or 'join'. If not set, both type will return. /// "Private" or "Register" or "Public". If set, only timelines whose visibility is specified one will return. /// The timeline list. - [HttpGet("timelines")] + [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task>> TimelineList([FromQuery][Username] string? relate, [FromQuery][RegularExpression("(own)|(join)")] string? relateType, [FromQuery] string? visibility) @@ -117,254 +114,50 @@ namespace Timeline.Controllers /// Get info of a timeline. /// /// The timeline name. - /// 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. - /// Same effect as If-Modified-Since header and take precedence than it. - /// If specified, will return 304 if not modified. /// The timeline info. - [HttpGet("timelines/{timeline}")] + [HttpGet("{timeline}")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status304NotModified)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> TimelineGet([FromRoute][GeneralTimelineName] string timeline, [FromQuery] string? checkUniqueId, [FromQuery(Name = "ifModifiedSince")] DateTime? queryIfModifiedSince, [FromHeader(Name = "If-Modified-Since")] DateTime? headerIfModifiedSince) + public async Task> TimelineGet([FromRoute][GeneralTimelineName] string timeline) { - DateTime? ifModifiedSince = null; - if (queryIfModifiedSince.HasValue) - { - ifModifiedSince = queryIfModifiedSince.Value; - } - 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(timelineId); - if (lastModified < ifModifiedSince.Value) - { - if (checkUniqueId != null) - { - var uniqueId = await _service.GetTimelineUniqueId(timelineId); - if (uniqueId == checkUniqueId) - { - returnNotModified = true; - } - } - else - { - returnNotModified = true; - } - } - } - - if (returnNotModified) - { - return StatusCode(StatusCodes.Status304NotModified); - } - else - { - var t = await _service.GetTimeline(timelineId); - var result = await _timelineMapper.MapToHttp(t, Url, this.GetOptionalUserId()); - return result; - } - } - - /// - /// Get posts of a timeline. - /// - /// The name of the timeline. - /// If set, only posts modified since the time will return. - /// If set to true, deleted post will also return. - /// The post list. - [HttpGet("timelines/{timeline}/posts")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> PostListGet([FromRoute][GeneralTimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted) - { - var timelineId = await _service.GetTimelineIdByName(timeline); - - if (!UserHasAllTimelineManagementPermission && !await _service.HasReadPermission(timelineId, this.GetOptionalUserId())) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - var posts = await _postService.GetPosts(timelineId, modifiedSince, includeDeleted ?? false); - - var result = await _timelineMapper.MapToHttp(posts, timeline, Url); + var t = await _service.GetTimeline(timelineId); + var result = await _timelineMapper.MapToHttp(t, Url, this.GetOptionalUserId()); return result; } /// - /// Get the data of a post. Usually a image post. - /// - /// Timeline name. - /// The id of the post. - /// If-None-Match header. - /// The 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 PostDataGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch) - { - _ = ifNoneMatch; - - 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(timelineId, post), - async () => await _postService.GetPostData(timelineId, post)); - } - catch (TimelinePostNotExistException) - { - return NotFound(ErrorResponse.TimelineController.PostNotExist()); - } - catch (TimelinePostNoDataException) - { - return BadRequest(ErrorResponse.TimelineController.PostNoData()); - } - } - - /// - /// Create a new post. + /// Change properties of a timeline. /// /// Timeline name. /// - /// Info of new post. - [HttpPost("timelines/{timeline}/posts")] + /// The new info. + [HttpPatch("{timeline}")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> PostPost([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body) + public async Task> TimelinePatch([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePatchRequest body) { var timelineId = await _service.GetTimelineIdByName(timeline); - var userId = this.GetUserId(); - if (!UserHasAllTimelineManagementPermission && !await _service.IsMemberOf(timelineId, userId)) + if (!UserHasAllTimelineManagementPermission && !await _service.HasManagePermission(timelineId, this.GetUserId())) { return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); } - var content = body.Content; - - TimelinePostEntity post; - - if (content.Type == TimelinePostContentTypes.Text) - { - var text = content.Text; - if (text == null) - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_TextContentTextRequired)); - } - post = await _postService.CreateTextPost(timelineId, userId, text, body.Time); - } - else if (content.Type == TimelinePostContentTypes.Image) - { - var base64Data = content.Data; - if (base64Data == null) - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataRequired)); - } - byte[] data; - try - { - data = Convert.FromBase64String(base64Data); - } - catch (FormatException) - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataNotBase64)); - } - - try - { - post = await _postService.CreateImagePost(timelineId, userId, data, body.Time); - } - catch (ImageException) - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataNotImage)); - } - } - else - { - return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ContentUnknownType)); - } - - var result = await _timelineMapper.MapToHttp(post, timeline, Url); - return result; - } - - /// - /// Delete a post. - /// - /// Timeline name. - /// Post id. - /// Info of deletion. - [HttpDelete("timelines/{timeline}/posts/{post}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task PostDelete([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post) - { - var timelineId = await _service.GetTimelineIdByName(timeline); - try { - 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 BadRequest(ErrorResponse.TimelineController.PostNotExist()); + await _service.ChangeProperty(timelineId, _mapper.Map(body)); + var t = await _service.GetTimeline(timelineId); + var result = await _timelineMapper.MapToHttp(t, Url, this.GetOptionalUserId()); + return result; } - } - - /// - /// Change properties of a timeline. - /// - /// Timeline name. - /// - /// The new info. - [HttpPatch("timelines/{timeline}")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> TimelinePatch([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePatchRequest body) - { - var timelineId = await _service.GetTimelineIdByName(timeline); - - if (!UserHasAllTimelineManagementPermission && !await _service.HasManagePermission(timelineId, this.GetUserId())) + catch (EntityAlreadyExistException) { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + return BadRequest(ErrorResponse.TimelineController.NameConflict()); } - await _service.ChangeProperty(timelineId, _mapper.Map(body)); - var t = await _service.GetTimeline(timelineId); - var result = await _timelineMapper.MapToHttp(t, Url, this.GetOptionalUserId()); - return result; } /// @@ -372,7 +165,7 @@ namespace Timeline.Controllers /// /// Timeline name. /// The new member's username. - [HttpPut("timelines/{timeline}/members/{member}")] + [HttpPut("{timeline}/members/{member}")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -404,7 +197,7 @@ namespace Timeline.Controllers /// /// Timeline name. /// The member's username. - [HttpDelete("timelines/{timeline}/members/{member}")] + [HttpDelete("{timeline}/members/{member}")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -436,7 +229,7 @@ namespace Timeline.Controllers /// /// /// Info of new timeline. - [HttpPost("timelines")] + [HttpPost] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -462,7 +255,7 @@ namespace Timeline.Controllers /// /// Timeline name. /// Info of deletion. - [HttpDelete("timelines/{timeline}")] + [HttpDelete("{timeline}")] [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -487,32 +280,5 @@ namespace Timeline.Controllers return BadRequest(ErrorResponse.TimelineController.NotExist()); } } - - [HttpPost("timelineop/changename")] - [Authorize] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> TimelineOpChangeName([FromBody] HttpTimelineChangeNameRequest body) - { - var timelineId = await _service.GetTimelineIdByName(body.OldName); - - if (!UserHasAllTimelineManagementPermission && !(await _service.HasManagePermission(timelineId, this.GetUserId()))) - { - return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); - } - - try - { - await _service.ChangeTimelineName(timelineId, body.NewName); - var timeline = await _service.GetTimeline(timelineId); - return await _timelineMapper.MapToHttp(timeline, Url, this.GetOptionalUserId()); - } - catch (EntityAlreadyExistException) - { - return BadRequest(ErrorResponse.TimelineController.NameConflict()); - } - } } } diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs new file mode 100644 index 00000000..afe9b36f --- /dev/null +++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs @@ -0,0 +1,213 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +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; + +namespace Timeline.Controllers +{ + /// + /// Operations about timeline. + /// + [ApiController] + [Route("timelines/{timeline}/posts")] + [CatchTimelineNotExistException] + [ProducesErrorResponseType(typeof(CommonResponse))] + public class TimelinePostController : Controller + { + private readonly ITimelineService _timelineService; + private readonly ITimelinePostService _postService; + + private readonly TimelineMapper _timelineMapper; + + /// + /// + /// + public TimelinePostController(ITimelineService timelineService, ITimelinePostService timelinePostService, TimelineMapper timelineMapper) + { + _timelineService = timelineService; + _postService = timelinePostService; + _timelineMapper = timelineMapper; + } + + private bool UserHasAllTimelineManagementPermission => this.UserHasPermission(UserPermission.AllTimelineManagement); + + /// + /// Get posts of a timeline. + /// + /// The name of the timeline. + /// If set, only posts modified since the time will return. + /// If set to true, deleted post will also return. + /// The post list. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> PostList([FromRoute][GeneralTimelineName] string timeline, [FromQuery] DateTime? modifiedSince, [FromQuery] bool? includeDeleted) + { + var timelineId = await _timelineService.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermission(timelineId, this.GetOptionalUserId())) + { + return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + } + + var posts = await _postService.GetPosts(timelineId, modifiedSince, includeDeleted ?? false); + + var result = await _timelineMapper.MapToHttp(posts, timeline, Url); + return result; + } + + /// + /// Get the data of a post. Usually a image post. + /// + /// Timeline name. + /// The id of the post. + /// If-None-Match header. + /// The data. + [HttpGet("{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 PostDataGet([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post, [FromHeader(Name = "If-None-Match")] string? ifNoneMatch) + { + _ = ifNoneMatch; + + var timelineId = await _timelineService.GetTimelineIdByName(timeline); + + if (!UserHasAllTimelineManagementPermission && !await _timelineService.HasReadPermission(timelineId, this.GetOptionalUserId())) + { + return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + } + + try + { + return await DataCacheHelper.GenerateActionResult(this, + () => _postService.GetPostDataETag(timelineId, post), + async () => await _postService.GetPostData(timelineId, post)); + } + catch (TimelinePostNotExistException) + { + return NotFound(ErrorResponse.TimelineController.PostNotExist()); + } + catch (TimelinePostNoDataException) + { + return BadRequest(ErrorResponse.TimelineController.PostNoData()); + } + } + + /// + /// Create a new post. + /// + /// Timeline name. + /// + /// Info of new post. + [HttpPost] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> PostPost([FromRoute][GeneralTimelineName] string timeline, [FromBody] HttpTimelinePostCreateRequest body) + { + var timelineId = await _timelineService.GetTimelineIdByName(timeline); + var userId = this.GetUserId(); + + if (!UserHasAllTimelineManagementPermission && !await _timelineService.IsMemberOf(timelineId, userId)) + { + return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid()); + } + + var content = body.Content; + + TimelinePostEntity post; + + if (content.Type == TimelinePostContentTypes.Text) + { + var text = content.Text; + if (text == null) + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_TextContentTextRequired)); + } + post = await _postService.CreateTextPost(timelineId, userId, text, body.Time); + } + else if (content.Type == TimelinePostContentTypes.Image) + { + var base64Data = content.Data; + if (base64Data == null) + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataRequired)); + } + byte[] data; + try + { + data = Convert.FromBase64String(base64Data); + } + catch (FormatException) + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataNotBase64)); + } + + try + { + post = await _postService.CreateImagePost(timelineId, userId, data, body.Time); + } + catch (ImageException) + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ImageContentDataNotImage)); + } + } + else + { + return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_ContentUnknownType)); + } + + var result = await _timelineMapper.MapToHttp(post, timeline, Url); + return result; + } + + /// + /// Delete a post. + /// + /// Timeline name. + /// Post id. + /// Info of deletion. + [HttpDelete("{post}")] + [Authorize] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task PostDelete([FromRoute][GeneralTimelineName] string timeline, [FromRoute] long post) + { + var timelineId = await _timelineService.GetTimelineIdByName(timeline); + + try + { + 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 BadRequest(ErrorResponse.TimelineController.PostNotExist()); + } + } + } +} diff --git a/BackEnd/Timeline/Models/Http/TimelineController.cs b/BackEnd/Timeline/Models/Http/TimelineController.cs index 257076f0..79be1826 100644 --- a/BackEnd/Timeline/Models/Http/TimelineController.cs +++ b/BackEnd/Timeline/Models/Http/TimelineController.cs @@ -58,6 +58,12 @@ namespace Timeline.Models.Http /// public class HttpTimelinePatchRequest { + /// + /// New name. Null for not change. + /// + [TimelineName] + public string? Name { get; set; } + /// /// New title. Null for not change. /// @@ -74,25 +80,6 @@ namespace Timeline.Models.Http public TimelineVisibility? Visibility { get; set; } } - /// - /// Change timeline name request model. - /// - public class HttpTimelineChangeNameRequest - { - /// - /// Old name of timeline. - /// - [Required] - [TimelineName] - public string OldName { get; set; } = default!; - /// - /// New name of timeline. - /// - [Required] - [TimelineName] - public string NewName { get; set; } = default!; - } - public class HttpTimelineControllerAutoMapperProfile : Profile { public HttpTimelineControllerAutoMapperProfile() diff --git a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs index 95418573..79a6fa1d 100644 --- a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs +++ b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs @@ -48,7 +48,7 @@ namespace Timeline.Models.Mapper isBookmark: userId is not null && await _bookmarkTimelineService.IsBookmark(userId.Value, entity.Id, false, false), 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 }) + posts: urlHelper.ActionLink(nameof(TimelinePostController.PostList), nameof(TimelinePostController)[0..^nameof(Controller).Length], new { timeline = timelineName }) ) ); } @@ -83,7 +83,7 @@ namespace Timeline.Models.Mapper ( type: TimelinePostContentTypes.Image, text: null, - url: urlHelper.ActionLink(nameof(TimelineController.PostDataGet), nameof(TimelineController)[0..^nameof(Controller).Length], new { timeline = timelineName, post = entity.LocalId }), + url: urlHelper.ActionLink(nameof(TimelinePostController.PostDataGet), nameof(TimelinePostController)[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)) diff --git a/BackEnd/Timeline/Properties/launchSettings.json b/BackEnd/Timeline/Properties/launchSettings.json index db58cd31..851fc6a8 100644 --- a/BackEnd/Timeline/Properties/launchSettings.json +++ b/BackEnd/Timeline/Properties/launchSettings.json @@ -2,11 +2,11 @@ "profiles": { "Dev": { "commandName": "Project", - "applicationUrl": "http://0.0.0.0:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_FRONTEND": "Proxy" - } + }, + "applicationUrl": "http://0.0.0.0:5000" }, "Dev-Mock": { "commandName": "Project", @@ -20,6 +20,14 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Staging" } + }, + "Dev-Windows": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_FRONTEND": "Proxy", + "TIMELINE_WORKDIR": "D:\\timeline-development" + } } } -} +} \ No newline at end of file diff --git a/BackEnd/Timeline/Services/TimelineService.cs b/BackEnd/Timeline/Services/TimelineService.cs index 1d1bb320..f4141752 100644 --- a/BackEnd/Timeline/Services/TimelineService.cs +++ b/BackEnd/Timeline/Services/TimelineService.cs @@ -49,6 +49,7 @@ namespace Timeline.Services public class TimelineChangePropertyParams { + public string? Name { get; set; } public string? Title { get; set; } public string? Description { get; set; } public TimelineVisibility? Visibility { get; set; } @@ -59,22 +60,6 @@ namespace Timeline.Services /// public interface ITimelineService : IBasicTimelineService { - /// - /// Get the timeline last modified time (not include name change). - /// - /// The id of the timeline. - /// The timeline modified time. - /// Thrown when timeline does not exist. - Task GetTimelineLastModifiedTime(long id); - - /// - /// Get the timeline unique id. - /// - /// The id of the timeline. - /// The timeline unique id. - /// Thrown when timeline does not exist. - Task GetTimelineUniqueId(long id); - /// /// Get the timeline info. /// @@ -90,6 +75,7 @@ namespace Timeline.Services /// The new properties. Null member means not to change. /// Thrown when is null. /// Thrown when timeline with given id does not exist. + /// Thrown when a timeline with new name already exists. Task ChangeProperty(long id, TimelineChangePropertyParams newProperties); /// @@ -180,20 +166,6 @@ namespace Timeline.Services /// The id of the timeline to delete. /// Thrown when the timeline does not exist. Task DeleteTimeline(long id); - - /// - /// Change name of a timeline. - /// - /// The timeline id. - /// The new timeline name. - /// Thrown when is null. - /// Thrown when is of invalid format. - /// Thrown when timeline does not exist. - /// Thrown when a timeline with new name already exists. - /// - /// You can only change name of general timeline. - /// - Task ChangeTimelineName(long id, string newTimelineName); } public class TimelineService : BasicTimelineService, ITimelineService @@ -222,26 +194,6 @@ namespace Timeline.Services } } - public async Task GetTimelineLastModifiedTime(long id) - { - var entity = await _database.Timelines.Where(t => t.Id == id).Select(t => new { t.LastModified }).SingleOrDefaultAsync(); - - if (entity is null) - throw new TimelineNotExistException(id); - - return entity.LastModified; - } - - public async Task GetTimelineUniqueId(long id) - { - var entity = await _database.Timelines.Where(t => t.Id == id).Select(t => new { t.UniqueId }).SingleOrDefaultAsync(); - - if (entity is null) - throw new TimelineNotExistException(id); - - return entity.UniqueId; - } - public async Task GetTimeline(long id) { var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync(); @@ -257,12 +209,29 @@ namespace Timeline.Services if (newProperties is null) throw new ArgumentNullException(nameof(newProperties)); + if (newProperties.Name is not null) + ValidateTimelineName(newProperties.Name, nameof(newProperties)); + var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync(); if (entity is null) throw new TimelineNotExistException(id); var changed = false; + var nameChanged = false; + + if (newProperties.Name is not null) + { + var conflict = await _database.Timelines.AnyAsync(t => t.Name == newProperties.Name); + + if (conflict) + throw new EntityAlreadyExistException(EntityNames.Timeline, null, ExceptionTimelineNameConflict); + + entity.Name = newProperties.Name; + + changed = true; + nameChanged = true; + } if (newProperties.Title != null) { @@ -286,6 +255,8 @@ namespace Timeline.Services { var currentTime = _clock.GetCurrentTime(); entity.LastModified = currentTime; + if (nameChanged) + entity.NameLastModified = currentTime; } await _database.SaveChangesAsync(); @@ -447,34 +418,6 @@ namespace Timeline.Services _database.Timelines.Remove(entity); await _database.SaveChangesAsync(); } - - public async Task ChangeTimelineName(long id, string newTimelineName) - { - if (newTimelineName == null) - throw new ArgumentNullException(nameof(newTimelineName)); - - ValidateTimelineName(newTimelineName, nameof(newTimelineName)); - - var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync(); - - if (entity is null) - throw new TimelineNotExistException(id); - - if (entity.Name == newTimelineName) return; - - var conflict = await _database.Timelines.AnyAsync(t => t.Name == newTimelineName); - - if (conflict) - throw new EntityAlreadyExistException(EntityNames.Timeline, null, ExceptionTimelineNameConflict); - - var now = _clock.GetCurrentTime(); - - entity.Name = newTimelineName; - entity.NameLastModified = now; - entity.LastModified = now; - - await _database.SaveChangesAsync(); - } } public static class TimelineServiceExtensions -- cgit v1.2.3