From 24c272403ba360f27acd68c2702c678a86063964 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 6 Mar 2021 19:32:38 +0800 Subject: refactor: Rename HttpCreateUserRequest to HttpUserPostRequest. --- .../Timeline.Tests/IntegratedTests/SearchTest.cs | 6 +++--- BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs | 18 ++++++++-------- BackEnd/Timeline/Controllers/UserController.cs | 3 +-- .../Timeline/Models/Http/HttpCreateUserRequest.cs | 24 ---------------------- .../Timeline/Models/Http/HttpUserPostRequest.cs | 24 ++++++++++++++++++++++ 5 files changed, 37 insertions(+), 38 deletions(-) delete mode 100644 BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs diff --git a/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs index 7782ed82..10a2f115 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs @@ -39,10 +39,10 @@ namespace Timeline.Tests.IntegratedTests var client = await CreateClientAsAdministrator(); { - await client.TestPostAsync("users", new HttpCreateUserRequest { Username = "hahaha", Password = "p" }); - await client.TestPostAsync("users", new HttpCreateUserRequest { Username = "bababa", Password = "p" }); + await client.TestPostAsync("users", new HttpUserPostRequest { Username = "hahaha", Password = "p" }); + await client.TestPostAsync("users", new HttpUserPostRequest { Username = "bababa", Password = "p" }); await client.TestPatchAsync("users/bababa", new HttpUserPatchRequest { Nickname = "hahaha" }); - await client.TestPostAsync("users", new HttpCreateUserRequest { Username = "gagaga", Password = "p" }); + await client.TestPostAsync("users", new HttpUserPostRequest { Username = "gagaga", Password = "p" }); } { diff --git a/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs index 664a0604..c728a280 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/UserTest.cs @@ -214,7 +214,7 @@ namespace Timeline.Tests.IntegratedTests { using var client = await CreateClientAsAdministrator(); { - var body = await client.TestPostAsync(createUserUrl, new HttpCreateUserRequest + var body = await client.TestPostAsync(createUserUrl, new HttpUserPostRequest { Username = "aaa", Password = "bbb", @@ -233,15 +233,15 @@ namespace Timeline.Tests.IntegratedTests public static IEnumerable Op_CreateUser_InvalidModel_Data() { - yield return new[] { new HttpCreateUserRequest { Username = "aaa" } }; - yield return new[] { new HttpCreateUserRequest { Password = "bbb" } }; - yield return new[] { new HttpCreateUserRequest { Username = "a!a", Password = "bbb" } }; - yield return new[] { new HttpCreateUserRequest { Username = "aaa", Password = "" } }; + yield return new[] { new HttpUserPostRequest { Username = "aaa" } }; + yield return new[] { new HttpUserPostRequest { Password = "bbb" } }; + yield return new[] { new HttpUserPostRequest { Username = "a!a", Password = "bbb" } }; + yield return new[] { new HttpUserPostRequest { Username = "aaa", Password = "" } }; } [Theory] [MemberData(nameof(Op_CreateUser_InvalidModel_Data))] - public async Task Op_CreateUser_InvalidModel(HttpCreateUserRequest body) + public async Task Op_CreateUser_InvalidModel(HttpUserPostRequest body) { using var client = await CreateClientAsAdministrator(); await client.TestPostAssertInvalidModelAsync(createUserUrl, body); @@ -251,7 +251,7 @@ namespace Timeline.Tests.IntegratedTests public async Task Op_CreateUser_UsernameConflict() { using var client = await CreateClientAsAdministrator(); - await client.TestPostAssertErrorAsync(createUserUrl, new HttpCreateUserRequest + await client.TestPostAssertErrorAsync(createUserUrl, new HttpUserPostRequest { Username = "user1", Password = "bbb", @@ -262,7 +262,7 @@ namespace Timeline.Tests.IntegratedTests public async Task Op_CreateUser_NoAuth_Unauthorized() { using var client = await CreateDefaultClient(); - await client.TestPostAssertUnauthorizedAsync(createUserUrl, new HttpCreateUserRequest + await client.TestPostAssertUnauthorizedAsync(createUserUrl, new HttpUserPostRequest { Username = "aaa", Password = "bbb", @@ -273,7 +273,7 @@ namespace Timeline.Tests.IntegratedTests public async Task Op_CreateUser_User_Forbid() { using var client = await CreateClientAsUser(); - await client.TestPostAssertForbiddenAsync(createUserUrl, new HttpCreateUserRequest + await client.TestPostAssertForbiddenAsync(createUserUrl, new HttpUserPostRequest { Username = "aaa", Password = "bbb", diff --git a/BackEnd/Timeline/Controllers/UserController.cs b/BackEnd/Timeline/Controllers/UserController.cs index b6eaf152..76d6042f 100644 --- a/BackEnd/Timeline/Controllers/UserController.cs +++ b/BackEnd/Timeline/Controllers/UserController.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using System; using System.Collections.Generic; using System.Threading.Tasks; using Timeline.Auth; @@ -69,7 +68,7 @@ namespace Timeline.Controllers [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task> Post([FromBody] HttpCreateUserRequest body) + public async Task> Post([FromBody] HttpUserPostRequest body) { try { diff --git a/BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs b/BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs deleted file mode 100644 index 7b221f73..00000000 --- a/BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Timeline.Controllers; -using Timeline.Models.Validation; - -namespace Timeline.Models.Http -{ - /// - /// Request model for . - /// - public class HttpCreateUserRequest - { - /// - /// Username of the new user. - /// - [Required, Username] - public string Username { get; set; } = default!; - - /// - /// Password of the new user. - /// - [Required, MinLength(1)] - public string Password { get; set; } = default!; - } -} diff --git a/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs b/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs new file mode 100644 index 00000000..c8cc004b --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpUserPostRequest.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using Timeline.Controllers; +using Timeline.Models.Validation; + +namespace Timeline.Models.Http +{ + /// + /// Request model for . + /// + public class HttpUserPostRequest + { + /// + /// Username of the new user. + /// + [Required, Username] + public string Username { get; set; } = default!; + + /// + /// Password of the new user. + /// + [Required, MinLength(1)] + public string Password { get; set; } = default!; + } +} -- cgit v1.2.3 From 8f09e3172f249a9f5d229040415a0569e9d1c01b Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 6 Mar 2021 22:51:53 +0800 Subject: feat: Auto translate url in markdown post. --- .../IntegratedTests/TimelinePostTest.cs | 32 +++++++++++++ BackEnd/Timeline.Tests/packages.lock.json | 6 +++ .../Timeline/Controllers/TimelinePostController.cs | 15 ++++++- BackEnd/Timeline/Services/MarkdownProcessor.cs | 52 ++++++++++++++++++++++ BackEnd/Timeline/Startup.cs | 2 + BackEnd/Timeline/Timeline.csproj | 1 + BackEnd/Timeline/packages.lock.json | 6 +++ 7 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 BackEnd/Timeline/Services/MarkdownProcessor.cs diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs index b91de6c2..f00d9b13 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs @@ -14,6 +14,7 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Jpeg; using System.Net; +using System.Text.RegularExpressions; namespace Timeline.Tests.IntegratedTests { @@ -36,6 +37,23 @@ namespace Timeline.Tests.IntegratedTests }; } + private static HttpTimelinePostCreateRequest CreateMarkdownPostRequest(string text, DateTime? time = null, string? color = null) + { + return new HttpTimelinePostCreateRequest() + { + Time = time, + Color = color, + DataList = new List() + { + new HttpTimelinePostCreateRequestData() + { + ContentType = MimeTypes.TextMarkdown, + Data = Convert.ToBase64String(Encoding.UTF8.GetBytes(text)) + } + } + }; + } + private readonly ITestOutputHelper _outputHelper; public TimelinePostTest(ITestOutputHelper outputHelper) @@ -586,5 +604,19 @@ namespace Timeline.Tests.IntegratedTests post2.Editable.Should().BeFalse(); } } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Post_Markdown_Url_Map(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", CreateMarkdownPostRequest("[aaa](1) ![bbb](2)")); + + var res = await client.GetAsync($"timelines/{generator(1)}/posts/{post.Id}/data"); + var markdown = await res.Content.ReadAsStringAsync(); + + markdown.Should().MatchRegex(@$"\[aaa\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/1\)"); + markdown.Should().MatchRegex(@$"\[bbb\]\(https?://.*/timelines/{generator(1)}/posts/{post.Id}/data/2\)"); + } } } diff --git a/BackEnd/Timeline.Tests/packages.lock.json b/BackEnd/Timeline.Tests/packages.lock.json index 50b90c3c..bdcfaf35 100644 --- a/BackEnd/Timeline.Tests/packages.lock.json +++ b/BackEnd/Timeline.Tests/packages.lock.json @@ -127,6 +127,11 @@ "System.Xml.XmlDocument": "4.3.0" } }, + "Markdig": { + "type": "Transitive", + "resolved": "0.23.0", + "contentHash": "jPPcnHGSDSedPvwZ6jiMJpvK3iJGA2djU6TFmEr6XK2BiYK1ier2lTHVwDt41XLxDbfGu5Dn42hKDzwqs049PA==" + }, "Microsoft.AspNetCore.Authorization": { "type": "Transitive", "resolved": "1.0.3", @@ -1981,6 +1986,7 @@ "dependencies": { "AutoMapper": "10.1.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "8.1.0", + "Markdig": "0.23.0", "Microsoft.AspNetCore.SpaServices.Extensions": "5.0.0", "Microsoft.EntityFrameworkCore": "5.0.0", "Microsoft.EntityFrameworkCore.Analyzers": "5.0.0", diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs index 4026d551..86c5c8cf 100644 --- a/BackEnd/Timeline/Controllers/TimelinePostController.cs +++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs @@ -32,14 +32,17 @@ namespace Timeline.Controllers private readonly TimelineMapper _timelineMapper; + private readonly MarkdownProcessor _markdownProcessor; + /// /// /// - public TimelinePostController(ITimelineService timelineService, ITimelinePostService timelinePostService, TimelineMapper timelineMapper) + public TimelinePostController(ITimelineService timelineService, ITimelinePostService timelinePostService, TimelineMapper timelineMapper, MarkdownProcessor markdownProcessor) { _timelineService = timelineService; _postService = timelinePostService; _timelineMapper = timelineMapper; + _markdownProcessor = markdownProcessor; } private bool UserHasAllTimelineManagementPermission => this.UserHasPermission(UserPermission.AllTimelineManagement); @@ -147,7 +150,15 @@ namespace Timeline.Controllers return await DataCacheHelper.GenerateActionResult(this, () => _postService.GetPostDataDigest(timelineId, post, dataIndex), - () => _postService.GetPostData(timelineId, post, dataIndex) + async () => + { + var data = await _postService.GetPostData(timelineId, post, dataIndex); + if (data.ContentType == MimeTypes.TextMarkdown) + { + return new ByteData(_markdownProcessor.Process(data.Data, Url, timeline, post), data.ContentType); + } + return data; + } ); } diff --git a/BackEnd/Timeline/Services/MarkdownProcessor.cs b/BackEnd/Timeline/Services/MarkdownProcessor.cs new file mode 100644 index 00000000..f34432cd --- /dev/null +++ b/BackEnd/Timeline/Services/MarkdownProcessor.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using Markdig; +using Markdig.Renderers.Normalize; +using Markdig.Syntax; +using Markdig.Syntax.Inlines; +using Microsoft.AspNetCore.Mvc; +using Timeline.Controllers; + +namespace Timeline.Services +{ + public class MarkdownProcessor + { + public string Process(string text, Func urlGenerator) + { + MarkdownDocument markdown = Markdown.Parse(text); + foreach (var link in markdown.Descendants().Where(e => e is LinkInline).Cast()) + { + if (int.TryParse(link.Url, out var dataIndex)) + { + link.Url = urlGenerator(dataIndex); + } + } + + var writer = new StringWriter(); + NormalizeRenderer renderer = new NormalizeRenderer(writer); + renderer.Render(markdown); + + return writer.ToString(); + } + + /// Convert data url to true url with post id. + public string Process(string text, IUrlHelper url, string timeline, long post) + { + return Process( + text, + dataIndex => url.ActionLink( + nameof(TimelinePostController.DataGet), + nameof(TimelinePostController)[0..^nameof(Controller).Length], + new { timeline, post, data_index = dataIndex } + ) + ); + } + + public byte[] Process(byte[] data, IUrlHelper url, string timeline, long post) + { + return Encoding.UTF8.GetBytes(Process(Encoding.UTF8.GetString(data), url, timeline, post)); + } + } +} diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs index 26ba3bfc..4f392b2d 100644 --- a/BackEnd/Timeline/Startup.cs +++ b/BackEnd/Timeline/Startup.cs @@ -125,6 +125,8 @@ namespace Timeline services.AddScoped(); + services.AddScoped(); + services.AddOpenApiDocs(); if (_frontEndMode == FrontEndMode.Mock) diff --git a/BackEnd/Timeline/Timeline.csproj b/BackEnd/Timeline/Timeline.csproj index 70536e00..6b565598 100644 --- a/BackEnd/Timeline/Timeline.csproj +++ b/BackEnd/Timeline/Timeline.csproj @@ -33,6 +33,7 @@ + all diff --git a/BackEnd/Timeline/packages.lock.json b/BackEnd/Timeline/packages.lock.json index 36442da7..da61aafc 100644 --- a/BackEnd/Timeline/packages.lock.json +++ b/BackEnd/Timeline/packages.lock.json @@ -23,6 +23,12 @@ "Microsoft.Extensions.Options": "3.0.0" } }, + "Markdig": { + "type": "Direct", + "requested": "[0.23.0, )", + "resolved": "0.23.0", + "contentHash": "jPPcnHGSDSedPvwZ6jiMJpvK3iJGA2djU6TFmEr6XK2BiYK1ier2lTHVwDt41XLxDbfGu5Dn42hKDzwqs049PA==" + }, "Microsoft.AspNetCore.SpaServices.Extensions": { "type": "Direct", "requested": "[5.0.0, )", -- cgit v1.2.3