From 0a0a61b60544a135a61394953bb5bb9dbbfeb241 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 2 Feb 2021 18:59:41 +0800 Subject: ... --- BackEnd/Timeline/Entities/TimelineEntity.cs | 3 + BackEnd/Timeline/Entities/TimelinePostEntity.cs | 3 + BackEnd/Timeline/Models/Http/BookmarkTimeline.cs | 23 --- BackEnd/Timeline/Models/Http/HighlightTimeline.cs | 23 --- .../Timeline/Models/Http/HttpAutoMapperProfile.cs | 15 ++ .../Models/Http/HttpBookmarkTimelineMoveRequest.cs | 23 +++ .../Models/Http/HttpChangePasswordRequest.cs | 23 +++ .../Timeline/Models/Http/HttpCreateTokenRequest.cs | 25 +++ .../Models/Http/HttpCreateTokenResponse.cs | 19 +++ .../Timeline/Models/Http/HttpCreateUserRequest.cs | 24 +++ .../Http/HttpHighlightTimelineMoveRequest.cs | 23 +++ BackEnd/Timeline/Models/Http/HttpTimeline.cs | 89 ++++++++++ .../Models/Http/HttpTimelineCreateRequest.cs | 18 ++ BackEnd/Timeline/Models/Http/HttpTimelineLinks.cs | 26 +++ .../Models/Http/HttpTimelinePatchRequest.cs | 37 +++++ BackEnd/Timeline/Models/Http/HttpTimelinePost.cs | 47 ++++++ .../Models/Http/HttpTimelinePostContent.cs | 35 ++++ .../Models/Http/HttpTimelinePostCreateRequest.cs | 19 +++ .../Http/HttpTimelinePostCreateRequestContent.cs | 24 +++ BackEnd/Timeline/Models/Http/HttpUser.cs | 46 ++++++ BackEnd/Timeline/Models/Http/HttpUserLinks.cs | 31 ++++ .../Timeline/Models/Http/HttpUserPatchRequest.cs | 30 ++++ .../Timeline/Models/Http/HttpVerifyTokenRequest.cs | 15 ++ .../Models/Http/HttpVerifyTokenResponse.cs | 16 ++ BackEnd/Timeline/Models/Http/Timeline.cs | 183 --------------------- BackEnd/Timeline/Models/Http/TimelineController.cs | 90 ---------- BackEnd/Timeline/Models/Http/TokenController.cs | 62 ------- BackEnd/Timeline/Models/Http/User.cs | 74 --------- BackEnd/Timeline/Models/Http/UserController.cs | 76 --------- BackEnd/Timeline/Models/Mapper/TimelineMapper.cs | 1 + .../Timeline/Models/Validation/ColorValidator.cs | 40 +++++ BackEnd/Timeline/Services/TimelineService.cs | 18 ++ 32 files changed, 650 insertions(+), 531 deletions(-) delete mode 100644 BackEnd/Timeline/Models/Http/BookmarkTimeline.cs delete mode 100644 BackEnd/Timeline/Models/Http/HighlightTimeline.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpAutoMapperProfile.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpBookmarkTimelineMoveRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpChangePasswordRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpCreateTokenRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpCreateTokenResponse.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpHighlightTimelineMoveRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpTimeline.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpTimelineCreateRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpTimelineLinks.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpTimelinePost.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpTimelinePostContent.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequestContent.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpUser.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpUserLinks.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpUserPatchRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpVerifyTokenRequest.cs create mode 100644 BackEnd/Timeline/Models/Http/HttpVerifyTokenResponse.cs delete mode 100644 BackEnd/Timeline/Models/Http/Timeline.cs delete mode 100644 BackEnd/Timeline/Models/Http/TimelineController.cs delete mode 100644 BackEnd/Timeline/Models/Http/TokenController.cs delete mode 100644 BackEnd/Timeline/Models/Http/User.cs delete mode 100644 BackEnd/Timeline/Models/Http/UserController.cs create mode 100644 BackEnd/Timeline/Models/Validation/ColorValidator.cs (limited to 'BackEnd') diff --git a/BackEnd/Timeline/Entities/TimelineEntity.cs b/BackEnd/Timeline/Entities/TimelineEntity.cs index 3e592673..23859cb3 100644 --- a/BackEnd/Timeline/Entities/TimelineEntity.cs +++ b/BackEnd/Timeline/Entities/TimelineEntity.cs @@ -41,6 +41,9 @@ namespace Timeline.Entities [Column("visibility")] public TimelineVisibility Visibility { get; set; } + [Column("color")] + public string? Color { get; set; } + [Column("create_time")] public DateTime CreateTime { get; set; } diff --git a/BackEnd/Timeline/Entities/TimelinePostEntity.cs b/BackEnd/Timeline/Entities/TimelinePostEntity.cs index 07367fba..39b11a5b 100644 --- a/BackEnd/Timeline/Entities/TimelinePostEntity.cs +++ b/BackEnd/Timeline/Entities/TimelinePostEntity.cs @@ -34,6 +34,9 @@ namespace Timeline.Entities [Column("extra_content")] public string? ExtraContent { get; set; } + [Column("color")] + public string? Color { get; set; } + [Column("time")] public DateTime Time { get; set; } diff --git a/BackEnd/Timeline/Models/Http/BookmarkTimeline.cs b/BackEnd/Timeline/Models/Http/BookmarkTimeline.cs deleted file mode 100644 index 14be1112..00000000 --- a/BackEnd/Timeline/Models/Http/BookmarkTimeline.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Timeline.Models.Validation; - -namespace Timeline.Models.Http -{ - /// - /// Move bookmark timeline request body model. - /// - public class HttpBookmarkTimelineMoveRequest - { - /// - /// Timeline name. - /// - [GeneralTimelineName] - public string Timeline { get; set; } = default!; - - /// - /// New position, starting at 1. - /// - [Required] - public long? NewPosition { get; set; } - } -} diff --git a/BackEnd/Timeline/Models/Http/HighlightTimeline.cs b/BackEnd/Timeline/Models/Http/HighlightTimeline.cs deleted file mode 100644 index 5af0e528..00000000 --- a/BackEnd/Timeline/Models/Http/HighlightTimeline.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Timeline.Models.Validation; - -namespace Timeline.Models.Http -{ - /// - /// Move highlight timeline request body model. - /// - public class HttpHighlightTimelineMoveRequest - { - /// - /// Timeline name. - /// - [GeneralTimelineName] - public string Timeline { get; set; } = default!; - - /// - /// New position, starting at 1. - /// - [Required] - public long? NewPosition { get; set; } - } -} diff --git a/BackEnd/Timeline/Models/Http/HttpAutoMapperProfile.cs b/BackEnd/Timeline/Models/Http/HttpAutoMapperProfile.cs new file mode 100644 index 00000000..426379b8 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpAutoMapperProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Timeline.Services; + +namespace Timeline.Models.Http +{ + + public class HttpAutoMapperProfile : Profile + { + public HttpAutoMapperProfile() + { + CreateMap(); + CreateMap(); + } + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpBookmarkTimelineMoveRequest.cs b/BackEnd/Timeline/Models/Http/HttpBookmarkTimelineMoveRequest.cs new file mode 100644 index 00000000..14be1112 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpBookmarkTimelineMoveRequest.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using Timeline.Models.Validation; + +namespace Timeline.Models.Http +{ + /// + /// Move bookmark timeline request body model. + /// + public class HttpBookmarkTimelineMoveRequest + { + /// + /// Timeline name. + /// + [GeneralTimelineName] + public string Timeline { get; set; } = default!; + + /// + /// New position, starting at 1. + /// + [Required] + public long? NewPosition { get; set; } + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpChangePasswordRequest.cs b/BackEnd/Timeline/Models/Http/HttpChangePasswordRequest.cs new file mode 100644 index 00000000..0397d7ce --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpChangePasswordRequest.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using Timeline.Controllers; + +namespace Timeline.Models.Http +{ + /// + /// Request model for . + /// + public class HttpChangePasswordRequest + { + /// + /// Old password. + /// + [Required(AllowEmptyStrings = false)] + public string OldPassword { get; set; } = default!; + + /// + /// New password. + /// + [Required(AllowEmptyStrings = false)] + public string NewPassword { get; set; } = default!; + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpCreateTokenRequest.cs b/BackEnd/Timeline/Models/Http/HttpCreateTokenRequest.cs new file mode 100644 index 00000000..2a20d490 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpCreateTokenRequest.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using Timeline.Controllers; + +namespace Timeline.Models.Http +{ + /// + /// Request model for . + /// + public class HttpCreateTokenRequest + { + /// + /// The username. + /// + public string Username { get; set; } = default!; + /// + /// The password. + /// + public string Password { get; set; } = default!; + /// + /// Optional token validation period. In days. If not specified, server will use a default one. + /// + [Range(1, 365)] + public int? Expire { get; set; } + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpCreateTokenResponse.cs b/BackEnd/Timeline/Models/Http/HttpCreateTokenResponse.cs new file mode 100644 index 00000000..78dd43c5 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpCreateTokenResponse.cs @@ -0,0 +1,19 @@ +using Timeline.Controllers; + +namespace Timeline.Models.Http +{ + /// + /// Response model for . + /// + public class HttpCreateTokenResponse + { + /// + /// The token created. + /// + public string Token { get; set; } = default!; + /// + /// The user owning the token. + /// + public HttpUser User { get; set; } = default!; + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs b/BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs new file mode 100644 index 00000000..7b221f73 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpCreateUserRequest.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 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/HttpHighlightTimelineMoveRequest.cs b/BackEnd/Timeline/Models/Http/HttpHighlightTimelineMoveRequest.cs new file mode 100644 index 00000000..5af0e528 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpHighlightTimelineMoveRequest.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using Timeline.Models.Validation; + +namespace Timeline.Models.Http +{ + /// + /// Move highlight timeline request body model. + /// + public class HttpHighlightTimelineMoveRequest + { + /// + /// Timeline name. + /// + [GeneralTimelineName] + public string Timeline { get; set; } = default!; + + /// + /// New position, starting at 1. + /// + [Required] + public long? NewPosition { get; set; } + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpTimeline.cs b/BackEnd/Timeline/Models/Http/HttpTimeline.cs new file mode 100644 index 00000000..87ebf0bb --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpTimeline.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; + +namespace Timeline.Models.Http +{ + /// + /// Info of a timeline. + /// + public class HttpTimeline + { + public HttpTimeline() { } + + public HttpTimeline(string uniqueId, string title, string name, DateTime nameLastModifed, string description, HttpUser owner, TimelineVisibility visibility, List members, string? color, DateTime createTime, DateTime lastModified, bool isHighlight, bool isBookmark, HttpTimelineLinks links) + { + UniqueId = uniqueId; + Title = title; + Name = name; + NameLastModifed = nameLastModifed; + Description = description; + Owner = owner; + Visibility = visibility; + Members = members; + Color = color; + CreateTime = createTime; + LastModified = lastModified; + IsHighlight = isHighlight; + IsBookmark = isBookmark; + _links = links; + } + + /// + /// Unique id. + /// + public string UniqueId { get; set; } = default!; + /// + /// Title. + /// + public string Title { get; set; } = default!; + /// + /// Name of timeline. + /// + public string Name { get; set; } = default!; + /// + /// Last modified time of timeline name. + /// + public DateTime NameLastModifed { get; set; } = default!; + /// + /// Timeline description. + /// + public string Description { get; set; } = default!; + /// + /// Owner of the timeline. + /// + public HttpUser Owner { get; set; } = default!; + /// + /// Visibility of the timeline. + /// + public TimelineVisibility Visibility { get; set; } +#pragma warning disable CA2227 // Collection properties should be read only + /// + /// Members of timeline. + /// + public List Members { get; set; } = default!; +#pragma warning restore CA2227 // Collection properties should be read only + /// + /// Color of timeline. + /// + public string? Color { get; set; } + /// + /// Create time of timeline. + /// + public DateTime CreateTime { get; set; } = default!; + /// + /// Last modified time of timeline. + /// + public DateTime LastModified { get; set; } = default!; + + public bool IsHighlight { get; set; } + + public bool IsBookmark { get; set; } + +#pragma warning disable CA1707 // Identifiers should not contain underscores + /// + /// Related links. + /// + public HttpTimelineLinks _links { get; set; } = default!; +#pragma warning restore CA1707 // Identifiers should not contain underscores + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpTimelineCreateRequest.cs b/BackEnd/Timeline/Models/Http/HttpTimelineCreateRequest.cs new file mode 100644 index 00000000..e9f57e46 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpTimelineCreateRequest.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using Timeline.Models.Validation; + +namespace Timeline.Models.Http +{ + /// + /// Create timeline request model. + /// + public class HttpTimelineCreateRequest + { + /// + /// Name of the new timeline. Must be a valid name. + /// + [Required] + [TimelineName] + public string Name { get; set; } = default!; + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpTimelineLinks.cs b/BackEnd/Timeline/Models/Http/HttpTimelineLinks.cs new file mode 100644 index 00000000..722c1338 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpTimelineLinks.cs @@ -0,0 +1,26 @@ +namespace Timeline.Models.Http +{ + + /// + /// Related links for timeline. + /// + public class HttpTimelineLinks + { + public HttpTimelineLinks() { } + + public HttpTimelineLinks(string self, string posts) + { + Self = self; + Posts = posts; + } + + /// + /// Self. + /// + public string Self { get; set; } = default!; + /// + /// Posts url. + /// + public string Posts { get; set; } = default!; + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs new file mode 100644 index 00000000..9accb6fc --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs @@ -0,0 +1,37 @@ +using Timeline.Models.Validation; + +namespace Timeline.Models.Http +{ + /// + /// Patch timeline request model. + /// + public class HttpTimelinePatchRequest + { + /// + /// New name. Null for not change. + /// + [TimelineName] + public string? Name { get; set; } + + /// + /// New title. Null for not change. + /// + public string? Title { get; set; } + + /// + /// New description. Null for not change. + /// + public string? Description { get; set; } + + /// + /// New visibility. Null for not change. + /// + public TimelineVisibility? Visibility { get; set; } + + /// + /// New color. Null for not change. + /// + [Color] + public string? Color { get; set; } + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs new file mode 100644 index 00000000..a563bea0 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs @@ -0,0 +1,47 @@ +using System; + +namespace Timeline.Models.Http +{ + /// + /// Info of a post. + /// + 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; + } + + /// + /// Post id. + /// + public long Id { get; set; } + /// + /// Content of the post. May be null if post is deleted. + /// + public HttpTimelinePostContent? Content { get; set; } + /// + /// True if post is deleted. + /// + public bool Deleted { get; set; } + /// + /// Post time. + /// + public DateTime Time { get; set; } + /// + /// The author. May be null if the user has been deleted. + /// + public HttpUser? Author { get; set; } = default!; + /// + /// Last updated time. + /// + public DateTime LastUpdated { get; set; } = default!; + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePostContent.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePostContent.cs new file mode 100644 index 00000000..55ff1ac2 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpTimelinePostContent.cs @@ -0,0 +1,35 @@ +namespace Timeline.Models.Http +{ + /// + /// Info of post content. + /// + public class HttpTimelinePostContent + { + public HttpTimelinePostContent() { } + + public HttpTimelinePostContent(string type, string? text, string? url, string? eTag) + { + Type = type; + Text = text; + Url = url; + ETag = eTag; + } + + /// + /// Type of the post content. + /// + public string Type { get; set; } = default!; + /// + /// If post is of text type. This is the text. + /// + public string? Text { get; set; } + /// + /// If post is of image type. This is the image url. + /// + public string? Url { get; set; } + /// + /// If post has data (currently it means it's a image post), this is the data etag. + /// + public string? ETag { get; set; } + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs new file mode 100644 index 00000000..cfbec029 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Timeline.Models.Http +{ + public class HttpTimelinePostCreateRequest + { + /// + /// Content of the new post. + /// + [Required] + public HttpTimelinePostCreateRequestContent Content { get; set; } = default!; + + /// + /// Time of the post. If not set, current time will be used. + /// + public DateTime? Time { get; set; } + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequestContent.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequestContent.cs new file mode 100644 index 00000000..f4b300a9 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequestContent.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; + +namespace Timeline.Models.Http +{ + /// + /// Content of post create request. + /// + public class HttpTimelinePostCreateRequestContent + { + /// + /// Type of post content. + /// + [Required] + public string Type { get; set; } = default!; + /// + /// If post is of text type, this is the text. + /// + public string? Text { get; set; } + /// + /// If post is of image type, this is base64 of image data. + /// + public string? Data { get; set; } + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpUser.cs b/BackEnd/Timeline/Models/Http/HttpUser.cs new file mode 100644 index 00000000..4b82264c --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpUser.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; + +namespace Timeline.Models.Http +{ + /// + /// Info of a user. + /// + public class HttpUser + { + public HttpUser() { } + + public HttpUser(string uniqueId, string username, string nickname, List permissions, HttpUserLinks links) + { + UniqueId = uniqueId; + Username = username; + Nickname = nickname; + Permissions = permissions; + _links = links; + } + + /// + /// Unique id. + /// + public string UniqueId { get; set; } = default!; + /// + /// Username. + /// + public string Username { get; set; } = default!; + /// + /// Nickname. + /// + public string Nickname { get; set; } = default!; +#pragma warning disable CA2227 // Collection properties should be read only + /// + /// The permissions of the user. + /// + public List Permissions { get; set; } = default!; +#pragma warning restore CA2227 // Collection properties should be read only +#pragma warning disable CA1707 // Identifiers should not contain underscores + /// + /// Related links. + /// + public HttpUserLinks _links { get; set; } = default!; +#pragma warning restore CA1707 // Identifiers should not contain underscores + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpUserLinks.cs b/BackEnd/Timeline/Models/Http/HttpUserLinks.cs new file mode 100644 index 00000000..d5f909c2 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpUserLinks.cs @@ -0,0 +1,31 @@ +namespace Timeline.Models.Http +{ + + /// + /// Related links for user. + /// + public class HttpUserLinks + { + public HttpUserLinks() { } + + public HttpUserLinks(string self, string avatar, string timeline) + { + Self = self; + Avatar = avatar; + Timeline = timeline; + } + + /// + /// Self. + /// + public string Self { get; set; } = default!; + /// + /// Avatar url. + /// + public string Avatar { get; set; } = default!; + /// + /// Personal timeline url. + /// + public string Timeline { get; set; } = default!; + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpUserPatchRequest.cs b/BackEnd/Timeline/Models/Http/HttpUserPatchRequest.cs new file mode 100644 index 00000000..e7a3d8e3 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpUserPatchRequest.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using Timeline.Controllers; +using Timeline.Models.Validation; + +namespace Timeline.Models.Http +{ + /// + /// Request model for . + /// + public class HttpUserPatchRequest + { + /// + /// New username. Null if not change. Need to be administrator. + /// + [Username] + public string? Username { get; set; } + + /// + /// New password. Null if not change. Need to be administrator. + /// + [MinLength(1)] + public string? Password { get; set; } + + /// + /// New nickname. Null if not change. Need to be administrator to change other's. + /// + [Nickname] + public string? Nickname { get; set; } + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpVerifyTokenRequest.cs b/BackEnd/Timeline/Models/Http/HttpVerifyTokenRequest.cs new file mode 100644 index 00000000..98f86455 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpVerifyTokenRequest.cs @@ -0,0 +1,15 @@ +using Timeline.Controllers; + +namespace Timeline.Models.Http +{ + /// + /// Request model for . + /// + public class HttpVerifyTokenRequest + { + /// + /// The token to verify. + /// + public string Token { get; set; } = default!; + } +} diff --git a/BackEnd/Timeline/Models/Http/HttpVerifyTokenResponse.cs b/BackEnd/Timeline/Models/Http/HttpVerifyTokenResponse.cs new file mode 100644 index 00000000..ae8eb018 --- /dev/null +++ b/BackEnd/Timeline/Models/Http/HttpVerifyTokenResponse.cs @@ -0,0 +1,16 @@ +using Timeline.Controllers; + +namespace Timeline.Models.Http +{ + + /// + /// Response model for . + /// + public class HttpVerifyTokenResponse + { + /// + /// The user owning the token. + /// + public HttpUser User { get; set; } = default!; + } +} diff --git a/BackEnd/Timeline/Models/Http/Timeline.cs b/BackEnd/Timeline/Models/Http/Timeline.cs deleted file mode 100644 index 5e5889f6..00000000 --- a/BackEnd/Timeline/Models/Http/Timeline.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Timeline.Models.Http -{ - /// - /// Info of post content. - /// - public class HttpTimelinePostContent - { - public HttpTimelinePostContent() { } - - public HttpTimelinePostContent(string type, string? text, string? url, string? eTag) - { - Type = type; - Text = text; - Url = url; - ETag = eTag; - } - - /// - /// Type of the post content. - /// - public string Type { get; set; } = default!; - /// - /// If post is of text type. This is the text. - /// - public string? Text { get; set; } - /// - /// If post is of image type. This is the image url. - /// - public string? Url { get; set; } - /// - /// If post has data (currently it means it's a image post), this is the data etag. - /// - public string? ETag { get; set; } - } - - /// - /// Info of a post. - /// - 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; - } - - /// - /// Post id. - /// - public long Id { get; set; } - /// - /// Content of the post. May be null if post is deleted. - /// - public HttpTimelinePostContent? Content { get; set; } - /// - /// True if post is deleted. - /// - public bool Deleted { get; set; } - /// - /// Post time. - /// - public DateTime Time { get; set; } - /// - /// The author. May be null if the user has been deleted. - /// - public HttpUser? Author { get; set; } = default!; - /// - /// Last updated time. - /// - public DateTime LastUpdated { get; set; } = default!; - } - - /// - /// Info of a timeline. - /// - public class HttpTimeline - { - public HttpTimeline() { } - - public HttpTimeline(string uniqueId, string title, string name, DateTime nameLastModifed, string description, HttpUser owner, TimelineVisibility visibility, List members, DateTime createTime, DateTime lastModified, bool isHighlight, bool isBookmark, HttpTimelineLinks links) - { - UniqueId = uniqueId; - Title = title; - Name = name; - NameLastModifed = nameLastModifed; - Description = description; - Owner = owner; - Visibility = visibility; - Members = members; - CreateTime = createTime; - LastModified = lastModified; - IsHighlight = isHighlight; - IsBookmark = isBookmark; - _links = links; - } - - /// - /// Unique id. - /// - public string UniqueId { get; set; } = default!; - /// - /// Title. - /// - public string Title { get; set; } = default!; - /// - /// Name of timeline. - /// - public string Name { get; set; } = default!; - /// - /// Last modified time of timeline name. - /// - public DateTime NameLastModifed { get; set; } = default!; - /// - /// Timeline description. - /// - public string Description { get; set; } = default!; - /// - /// Owner of the timeline. - /// - public HttpUser Owner { get; set; } = default!; - /// - /// Visibility of the timeline. - /// - public TimelineVisibility Visibility { get; set; } -#pragma warning disable CA2227 // Collection properties should be read only - /// - /// Members of timeline. - /// - public List Members { get; set; } = default!; -#pragma warning restore CA2227 // Collection properties should be read only - /// - /// Create time of timeline. - /// - public DateTime CreateTime { get; set; } = default!; - /// - /// Last modified time of timeline. - /// - public DateTime LastModified { get; set; } = default!; - - public bool IsHighlight { get; set; } - - public bool IsBookmark { get; set; } - -#pragma warning disable CA1707 // Identifiers should not contain underscores - /// - /// Related links. - /// - public HttpTimelineLinks _links { get; set; } = default!; -#pragma warning restore CA1707 // Identifiers should not contain underscores - } - - /// - /// Related links for timeline. - /// - public class HttpTimelineLinks - { - public HttpTimelineLinks() { } - - public HttpTimelineLinks(string self, string posts) - { - Self = self; - Posts = posts; - } - - /// - /// Self. - /// - public string Self { get; set; } = default!; - /// - /// Posts url. - /// - public string Posts { get; set; } = default!; - } -} diff --git a/BackEnd/Timeline/Models/Http/TimelineController.cs b/BackEnd/Timeline/Models/Http/TimelineController.cs deleted file mode 100644 index 79be1826..00000000 --- a/BackEnd/Timeline/Models/Http/TimelineController.cs +++ /dev/null @@ -1,90 +0,0 @@ -using AutoMapper; -using System; -using System.ComponentModel.DataAnnotations; -using Timeline.Models.Validation; -using Timeline.Services; - -namespace Timeline.Models.Http -{ - /// - /// Content of post create request. - /// - public class HttpTimelinePostCreateRequestContent - { - /// - /// Type of post content. - /// - [Required] - public string Type { get; set; } = default!; - /// - /// If post is of text type, this is the text. - /// - public string? Text { get; set; } - /// - /// If post is of image type, this is base64 of image data. - /// - public string? Data { get; set; } - } - - public class HttpTimelinePostCreateRequest - { - /// - /// Content of the new post. - /// - [Required] - public HttpTimelinePostCreateRequestContent Content { get; set; } = default!; - - /// - /// Time of the post. If not set, current time will be used. - /// - public DateTime? Time { get; set; } - } - - /// - /// Create timeline request model. - /// - public class HttpTimelineCreateRequest - { - /// - /// Name of the new timeline. Must be a valid name. - /// - [Required] - [TimelineName] - public string Name { get; set; } = default!; - } - - /// - /// Patch timeline request model. - /// - public class HttpTimelinePatchRequest - { - /// - /// New name. Null for not change. - /// - [TimelineName] - public string? Name { get; set; } - - /// - /// New title. Null for not change. - /// - public string? Title { get; set; } - - /// - /// New description. Null for not change. - /// - public string? Description { get; set; } - - /// - /// New visibility. Null for not change. - /// - public TimelineVisibility? Visibility { get; set; } - } - - public class HttpTimelineControllerAutoMapperProfile : Profile - { - public HttpTimelineControllerAutoMapperProfile() - { - CreateMap(); - } - } -} diff --git a/BackEnd/Timeline/Models/Http/TokenController.cs b/BackEnd/Timeline/Models/Http/TokenController.cs deleted file mode 100644 index a5cbba14..00000000 --- a/BackEnd/Timeline/Models/Http/TokenController.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Timeline.Controllers; - -namespace Timeline.Models.Http -{ - /// - /// Request model for . - /// - public class HttpCreateTokenRequest - { - /// - /// The username. - /// - public string Username { get; set; } = default!; - /// - /// The password. - /// - public string Password { get; set; } = default!; - /// - /// Optional token validation period. In days. If not specified, server will use a default one. - /// - [Range(1, 365)] - public int? Expire { get; set; } - } - - /// - /// Response model for . - /// - public class HttpCreateTokenResponse - { - /// - /// The token created. - /// - public string Token { get; set; } = default!; - /// - /// The user owning the token. - /// - public HttpUser User { get; set; } = default!; - } - - /// - /// Request model for . - /// - public class HttpVerifyTokenRequest - { - /// - /// The token to verify. - /// - public string Token { get; set; } = default!; - } - - /// - /// Response model for . - /// - public class HttpVerifyTokenResponse - { - /// - /// The user owning the token. - /// - public HttpUser User { get; set; } = default!; - } -} diff --git a/BackEnd/Timeline/Models/Http/User.cs b/BackEnd/Timeline/Models/Http/User.cs deleted file mode 100644 index 994c08bf..00000000 --- a/BackEnd/Timeline/Models/Http/User.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Generic; - -namespace Timeline.Models.Http -{ - /// - /// Info of a user. - /// - public class HttpUser - { - public HttpUser() { } - - public HttpUser(string uniqueId, string username, string nickname, List permissions, HttpUserLinks links) - { - UniqueId = uniqueId; - Username = username; - Nickname = nickname; - Permissions = permissions; - _links = links; - } - - /// - /// Unique id. - /// - public string UniqueId { get; set; } = default!; - /// - /// Username. - /// - public string Username { get; set; } = default!; - /// - /// Nickname. - /// - public string Nickname { get; set; } = default!; -#pragma warning disable CA2227 // Collection properties should be read only - /// - /// The permissions of the user. - /// - public List Permissions { get; set; } = default!; -#pragma warning restore CA2227 // Collection properties should be read only -#pragma warning disable CA1707 // Identifiers should not contain underscores - /// - /// Related links. - /// - public HttpUserLinks _links { get; set; } = default!; -#pragma warning restore CA1707 // Identifiers should not contain underscores - } - - /// - /// Related links for user. - /// - public class HttpUserLinks - { - public HttpUserLinks() { } - - public HttpUserLinks(string self, string avatar, string timeline) - { - Self = self; - Avatar = avatar; - Timeline = timeline; - } - - /// - /// Self. - /// - public string Self { get; set; } = default!; - /// - /// Avatar url. - /// - public string Avatar { get; set; } = default!; - /// - /// Personal timeline url. - /// - public string Timeline { get; set; } = default!; - } -} diff --git a/BackEnd/Timeline/Models/Http/UserController.cs b/BackEnd/Timeline/Models/Http/UserController.cs deleted file mode 100644 index 1b4d09ec..00000000 --- a/BackEnd/Timeline/Models/Http/UserController.cs +++ /dev/null @@ -1,76 +0,0 @@ -using AutoMapper; -using System.ComponentModel.DataAnnotations; -using Timeline.Controllers; -using Timeline.Models.Validation; -using Timeline.Services; - -namespace Timeline.Models.Http -{ - /// - /// Request model for . - /// - public class HttpUserPatchRequest - { - /// - /// New username. Null if not change. Need to be administrator. - /// - [Username] - public string? Username { get; set; } - - /// - /// New password. Null if not change. Need to be administrator. - /// - [MinLength(1)] - public string? Password { get; set; } - - /// - /// New nickname. Null if not change. Need to be administrator to change other's. - /// - [Nickname] - public string? Nickname { get; set; } - } - - /// - /// 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!; - } - - /// - /// Request model for . - /// - public class HttpChangePasswordRequest - { - /// - /// Old password. - /// - [Required(AllowEmptyStrings = false)] - public string OldPassword { get; set; } = default!; - - /// - /// New password. - /// - [Required(AllowEmptyStrings = false)] - public string NewPassword { get; set; } = default!; - } - - public class HttpUserControllerModelAutoMapperProfile : Profile - { - public HttpUserControllerModelAutoMapperProfile() - { - CreateMap(); - } - } -} diff --git a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs index 79a6fa1d..25abf0ba 100644 --- a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs +++ b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs @@ -42,6 +42,7 @@ namespace Timeline.Models.Mapper owner: await _userMapper.MapToHttp(entity.Owner, urlHelper), visibility: entity.Visibility, members: await _userMapper.MapToHttp(entity.Members.Select(m => m.User).ToList(), urlHelper), + color: entity.Color, createTime: entity.CreateTime, lastModified: entity.LastModified, isHighlight: await _highlightTimelineService.IsHighlightTimeline(entity.Id), diff --git a/BackEnd/Timeline/Models/Validation/ColorValidator.cs b/BackEnd/Timeline/Models/Validation/ColorValidator.cs new file mode 100644 index 00000000..c5ad833d --- /dev/null +++ b/BackEnd/Timeline/Models/Validation/ColorValidator.cs @@ -0,0 +1,40 @@ +using System; + +namespace Timeline.Models.Validation +{ + public class ColorValidator : Validator + { + protected override (bool, string) DoValidate(string value) + { + if (!value.StartsWith('#')) + { + return (false, "Color must starts with '#'."); + } + + if (value.Length != 7) + { + return (false, "A color string must have 7 chars."); + } + + for (int i = 1; i < 7; i++) + { + var c = value[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' || c <= 'f') || (c >= 'A' | c <= 'F'))) + { + return (false, $"Char at index {i} is not a hex character."); + } + } + + return (true, GetSuccessMessage()); + } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] + public class ColorAttribute : ValidateWithAttribute + { + public ColorAttribute() : base(typeof(ColorValidator)) + { + + } + } +} diff --git a/BackEnd/Timeline/Services/TimelineService.cs b/BackEnd/Timeline/Services/TimelineService.cs index f4141752..bed1c99b 100644 --- a/BackEnd/Timeline/Services/TimelineService.cs +++ b/BackEnd/Timeline/Services/TimelineService.cs @@ -53,6 +53,7 @@ namespace Timeline.Services public string? Title { get; set; } public string? Description { get; set; } public TimelineVisibility? Visibility { get; set; } + public string? Color { get; set; } } /// @@ -186,6 +187,8 @@ namespace Timeline.Services private readonly TimelineNameValidator _timelineNameValidator = new TimelineNameValidator(); + private readonly ColorValidator _colorValidator = new ColorValidator(); + private void ValidateTimelineName(string name, string paramName) { if (!_timelineNameValidator.Validate(name, out var message)) @@ -212,6 +215,15 @@ namespace Timeline.Services if (newProperties.Name is not null) ValidateTimelineName(newProperties.Name, nameof(newProperties)); + if (newProperties.Color is not null) + { + var (result, message) = _colorValidator.Validate(newProperties.Color); + if (!result) + { + throw new ArgumentException(message, nameof(newProperties)); + } + } + var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync(); if (entity is null) @@ -251,6 +263,12 @@ namespace Timeline.Services entity.Visibility = newProperties.Visibility.Value; } + if (newProperties.Color is not null) + { + changed = true; + entity.Color = newProperties.Color; + } + if (changed) { var currentTime = _clock.GetCurrentTime(); -- cgit v1.2.3 From 78b3cca725bd508e248749eb3ca5cd6f3ea0f8ec Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 2 Feb 2021 20:26:32 +0800 Subject: test: Add timeline color test. --- .../Timeline.Tests/IntegratedTests/TimelineTest.cs | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'BackEnd') diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs index 28fcb9fa..ec9a6d83 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs @@ -389,5 +389,29 @@ namespace Timeline.Tests.IntegratedTests } } } + + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Color(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + { + var timeline = await client.TestGetAsync($"timelines/{generator(1)}"); + timeline.Color.Should().Be(null); + } + + await client.TestPatchAssertInvalidModelAsync($"timelines/{generator(1)}", new HttpTimelinePatchRequest { Color = "!!!" }); + + { + var timeline = await client.TestPatchAsync($"timelines/{generator(1)}", new HttpTimelinePatchRequest { Color = "#112233" }); + timeline.Color.Should().Be("#112233"); + } + + { + var timeline = await client.TestGetAsync($"timelines/{generator(1)}"); + timeline.Color.Should().Be("#112233"); + } + } } } -- cgit v1.2.3 From 1af0c82144a0f28f86922ecda04b159a553c699e Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 4 Feb 2021 20:54:18 +0800 Subject: ... --- .../Timeline/Controllers/TimelinePostController.cs | 4 +- BackEnd/Timeline/Models/Validation/Validator.cs | 2 +- BackEnd/Timeline/Services/TimelinePostService.cs | 122 ++++++++++++--------- 3 files changed, 71 insertions(+), 57 deletions(-) (limited to 'BackEnd') diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs index afe9b36f..6b7ba411 100644 --- a/BackEnd/Timeline/Controllers/TimelinePostController.cs +++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs @@ -142,7 +142,7 @@ namespace Timeline.Controllers { return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_TextContentTextRequired)); } - post = await _postService.CreateTextPost(timelineId, userId, text, body.Time); + post = await _postService.CreateTextPost(timelineId, userId, text, new TimelinePostCommonProperties { Time = body.Time }); } else if (content.Type == TimelinePostContentTypes.Image) { @@ -163,7 +163,7 @@ namespace Timeline.Controllers try { - post = await _postService.CreateImagePost(timelineId, userId, data, body.Time); + post = await _postService.CreateImagePost(timelineId, userId, data, new TimelinePostCommonProperties { Time = body.Time }); } catch (ImageException) { diff --git a/BackEnd/Timeline/Models/Validation/Validator.cs b/BackEnd/Timeline/Models/Validation/Validator.cs index b7e754d3..ec6cc0af 100644 --- a/BackEnd/Timeline/Models/Validation/Validator.cs +++ b/BackEnd/Timeline/Models/Validation/Validator.cs @@ -51,7 +51,7 @@ namespace Timeline.Models.Validation public (bool, string) Validate(object? value) { - if (value == null) + if (value is null) { if (PermitNull) return (true, GetSuccessMessage()); diff --git a/BackEnd/Timeline/Services/TimelinePostService.cs b/BackEnd/Timeline/Services/TimelinePostService.cs index a8bdbf92..c2b773ff 100644 --- a/BackEnd/Timeline/Services/TimelinePostService.cs +++ b/BackEnd/Timeline/Services/TimelinePostService.cs @@ -8,6 +8,7 @@ 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.TimelineService; @@ -23,6 +24,14 @@ namespace Timeline.Services public DateTime? LastModified { get; set; } // TODO: Why nullable? } + public class TimelinePostCommonProperties + { + public string? Color { get; set; } + + /// If not set, current time is used. + public DateTime? Time { get; set; } + } + public interface ITimelinePostService { /// @@ -64,12 +73,12 @@ namespace Timeline.Services /// The id of the timeline to create post against. /// The author's user id. /// The content text. - /// The time of the post. If null, then current time is used. + /// Some properties. /// The info of the created post. /// Thrown when is null. /// Thrown when timeline does not exist. /// Thrown if user of does not exist. - Task CreateTextPost(long timelineId, long authorId, string text, DateTime? time); + Task CreateTextPost(long timelineId, long authorId, string text, TimelinePostCommonProperties? properties = null); /// /// Create a new image post in timeline. @@ -77,13 +86,13 @@ namespace Timeline.Services /// The id of the timeline to create post against. /// The author's user id. /// The image data. - /// The time of the post. If null, then use current time. + /// Some properties. /// The info of the created post. /// Thrown when is null. /// Thrown when timeline does not exist. /// Thrown if user of does not exist. /// Thrown if data is not a image. Validated by . - Task CreateImagePost(long timelineId, long authorId, byte[] imageData, DateTime? time); + Task CreateImagePost(long timelineId, long authorId, byte[] imageData, TimelinePostCommonProperties? properties = null); /// /// Delete a post. @@ -128,17 +137,18 @@ namespace Timeline.Services private readonly ILogger _logger; private readonly DatabaseContext _database; private readonly IBasicTimelineService _basicTimelineService; - private readonly IUserService _userService; + private readonly IBasicUserService _basicUserService; private readonly IDataManager _dataManager; private readonly IImageValidator _imageValidator; private readonly IClock _clock; + private readonly ColorValidator _colorValidator = new ColorValidator(); - public TimelinePostService(ILogger logger, DatabaseContext database, IBasicTimelineService basicTimelineService, IUserService userService, IDataManager dataManager, IImageValidator imageValidator, IClock clock) + public TimelinePostService(ILogger logger, DatabaseContext database, IBasicTimelineService basicTimelineService, IBasicUserService basicUserService, IDataManager dataManager, IImageValidator imageValidator, IClock clock) { _logger = logger; _database = database; _basicTimelineService = basicTimelineService; - _userService = userService; + _basicUserService = basicUserService; _dataManager = dataManager; _imageValidator = imageValidator; _clock = clock; @@ -150,6 +160,12 @@ namespace Timeline.Services throw new TimelineNotExistException(timelineId); } + private async Task CheckUserExistence(long userId) + { + if (!await _basicUserService.CheckUserExistence(userId)) + throw new UserNotExistException(userId); + } + public async Task> GetPosts(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false) { await CheckTimelineExistence(timelineId); @@ -238,79 +254,77 @@ namespace Timeline.Services }; } - public async Task CreateTextPost(long timelineId, long authorId, string text, DateTime? time) + private async Task GeneralCreatePost(long timelineId, long authorId, TimelinePostCommonProperties? properties, Func saveContent) { - if (text is null) - throw new ArgumentNullException(nameof(text)); + if (properties is not null) + { + if (!_colorValidator.Validate(properties.Color, out var message)) + { + throw new ArgumentException(message, nameof(properties)); + } + properties.Time = properties.Time?.MyToUtc(); + } await CheckTimelineExistence(timelineId); - - time = time?.MyToUtc(); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); - - var author = await _userService.GetUser(authorId); + await CheckUserExistence(authorId); var currentTime = _clock.GetCurrentTime(); - var finalTime = time ?? currentTime; - - timelineEntity.CurrentPostLocalId += 1; + var finalTime = properties?.Time ?? currentTime; var postEntity = new TimelinePostEntity { - LocalId = timelineEntity.CurrentPostLocalId, - ContentType = TimelinePostContentTypes.Text, - Content = text, AuthorId = authorId, TimelineId = timelineId, Time = finalTime, - LastUpdated = currentTime + LastUpdated = currentTime, + Color = properties?.Color }; + + await saveContent(postEntity); + + var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); + timelineEntity.CurrentPostLocalId += 1; + postEntity.LocalId = timelineEntity.CurrentPostLocalId; + _database.TimelinePosts.Add(postEntity); + await _database.SaveChangesAsync(); return postEntity; } - public async Task CreateImagePost(long timelineId, long authorId, byte[] data, DateTime? time) + public async Task CreateTextPost(long timelineId, long authorId, string text, TimelinePostCommonProperties? properties = null) { - if (data is null) - throw new ArgumentNullException(nameof(data)); - - await CheckTimelineExistence(timelineId); - - time = time?.MyToUtc(); - - var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); - - var author = await _userService.GetUser(authorId); - - var imageFormat = await _imageValidator.Validate(data); + if (text is null) + throw new ArgumentNullException(nameof(text)); - var imageFormatText = imageFormat.DefaultMimeType; + return await GeneralCreatePost(timelineId, authorId, properties, (entity) => + { + entity.ContentType = TimelinePostContentTypes.Text; + entity.Content = text; - var tag = await _dataManager.RetainEntry(data); + return Task.CompletedTask; + }); + } - var currentTime = _clock.GetCurrentTime(); - var finalTime = time ?? currentTime; + public async Task CreateImagePost(long timelineId, long authorId, byte[] data, TimelinePostCommonProperties? properties = null) + { + if (data is null) + throw new ArgumentNullException(nameof(data)); - timelineEntity.CurrentPostLocalId += 1; + await CheckTimelineExistence(timelineId); - var postEntity = new TimelinePostEntity + return await GeneralCreatePost(timelineId, authorId, properties, async (entity) => { - LocalId = timelineEntity.CurrentPostLocalId, - ContentType = TimelinePostContentTypes.Image, - Content = tag, - ExtraContent = imageFormatText, - AuthorId = authorId, - TimelineId = timelineId, - Time = finalTime, - LastUpdated = currentTime - }; - _database.TimelinePosts.Add(postEntity); - await _database.SaveChangesAsync(); + var imageFormat = await _imageValidator.Validate(data); + var imageFormatText = imageFormat.DefaultMimeType; - return postEntity; + var tag = await _dataManager.RetainEntry(data); + + entity.ContentType = TimelinePostContentTypes.Image; + entity.Content = tag; + entity.ExtraContent = imageFormatText; + }); } public async Task DeletePost(long timelineId, long postId) -- cgit v1.2.3 From c6a4b88794cb87c25757542ad0642e9df56ddf0f Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 4 Feb 2021 21:26:01 +0800 Subject: ... --- .../IntegratedTests/TimelinePostTest.cs | 27 ++++++++++++++++++++++ .../Timeline/Controllers/TimelinePostController.cs | 6 +++-- BackEnd/Timeline/Models/Http/HttpTimelinePost.cs | 7 +++++- .../Models/Http/HttpTimelinePostCreateRequest.cs | 7 ++++++ BackEnd/Timeline/Models/Mapper/TimelineMapper.cs | 1 + 5 files changed, 45 insertions(+), 3 deletions(-) (limited to 'BackEnd') diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs index ae7afda1..f05ed7af 100644 --- a/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs +++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelinePostTest.cs @@ -473,5 +473,32 @@ namespace Timeline.Tests.IntegratedTests } } + [Theory] + [MemberData(nameof(TimelineNameGeneratorTestData))] + public async Task Color(TimelineNameGenerator generator) + { + using var client = await CreateClientAsUser(); + + HttpTimelinePostCreateRequestContent CreateRequestContent() => new() + { + Type = "text", + Text = "aaa" + }; + + await client.TestPostAssertInvalidModelAsync($"timelines/{generator(1)}/posts", new HttpTimelinePostCreateRequest + { + Content = CreateRequestContent(), + Color = "#1" + }); + + { + var post = await client.TestPostAsync($"timelines/{generator(1)}/posts", new HttpTimelinePostCreateRequest + { + Content = CreateRequestContent(), + Color = "#aabbcc" + }); + post.Color.Should().Be("#aabbcc"); + } + } } } diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs index 6b7ba411..3f31decf 100644 --- a/BackEnd/Timeline/Controllers/TimelinePostController.cs +++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs @@ -135,6 +135,8 @@ namespace Timeline.Controllers TimelinePostEntity post; + TimelinePostCommonProperties properties = new TimelinePostCommonProperties { Color = body.Color, Time = body.Time }; + if (content.Type == TimelinePostContentTypes.Text) { var text = content.Text; @@ -142,7 +144,7 @@ namespace Timeline.Controllers { return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_TextContentTextRequired)); } - post = await _postService.CreateTextPost(timelineId, userId, text, new TimelinePostCommonProperties { Time = body.Time }); + post = await _postService.CreateTextPost(timelineId, userId, text, properties); } else if (content.Type == TimelinePostContentTypes.Image) { @@ -163,7 +165,7 @@ namespace Timeline.Controllers try { - post = await _postService.CreateImagePost(timelineId, userId, data, new TimelinePostCommonProperties { Time = body.Time }); + post = await _postService.CreateImagePost(timelineId, userId, data, properties); } catch (ImageException) { diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs index a563bea0..5981d7a4 100644 --- a/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs +++ b/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs @@ -9,13 +9,14 @@ namespace Timeline.Models.Http { public HttpTimelinePost() { } - public HttpTimelinePost(long id, HttpTimelinePostContent? content, bool deleted, DateTime time, HttpUser? author, DateTime lastUpdated) + public HttpTimelinePost(long id, HttpTimelinePostContent? content, bool deleted, DateTime time, HttpUser? author, string? color, DateTime lastUpdated) { Id = id; Content = content; Deleted = deleted; Time = time; Author = author; + Color = color; LastUpdated = lastUpdated; } @@ -40,6 +41,10 @@ namespace Timeline.Models.Http /// public HttpUser? Author { get; set; } = default!; /// + /// The color. + /// + public string? Color { get; set; } + /// /// Last updated time. /// public DateTime LastUpdated { get; set; } = default!; diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs index cfbec029..b25adf36 100644 --- a/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs +++ b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations; +using Timeline.Models.Validation; namespace Timeline.Models.Http { @@ -15,5 +16,11 @@ namespace Timeline.Models.Http /// Time of the post. If not set, current time will be used. /// public DateTime? Time { get; set; } + + /// + /// Color of the post. + /// + [Color] + public string? Color { get; set; } } } diff --git a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs index 25abf0ba..94e55237 100644 --- a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs +++ b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs @@ -106,6 +106,7 @@ namespace Timeline.Models.Mapper deleted: content is null, time: entity.Time, author: author, + color: entity.Color, lastUpdated: entity.LastUpdated ); } -- cgit v1.2.3 From c09ec62c64fe2d7cd09f7a2d9989ad96b43606a3 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 4 Feb 2021 21:33:42 +0800 Subject: database: Upgrade database. --- .../Migrations/20210204133241_AddColor.Designer.cs | 506 +++++++++++++++++++++ .../Timeline/Migrations/20210204133241_AddColor.cs | 33 ++ .../Migrations/DatabaseContextModelSnapshot.cs | 8 + 3 files changed, 547 insertions(+) create mode 100644 BackEnd/Timeline/Migrations/20210204133241_AddColor.Designer.cs create mode 100644 BackEnd/Timeline/Migrations/20210204133241_AddColor.cs (limited to 'BackEnd') diff --git a/BackEnd/Timeline/Migrations/20210204133241_AddColor.Designer.cs b/BackEnd/Timeline/Migrations/20210204133241_AddColor.Designer.cs new file mode 100644 index 00000000..8f8c2495 --- /dev/null +++ b/BackEnd/Timeline/Migrations/20210204133241_AddColor.Designer.cs @@ -0,0 +1,506 @@ +// +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("20210204133241_AddColor")] + partial class AddColor + { + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Rank") + .HasColumnType("INTEGER") + .HasColumnName("rank"); + + b.Property("TimelineId") + .HasColumnType("INTEGER") + .HasColumnName("timeline"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Ref") + .HasColumnType("INTEGER") + .HasColumnName("ref"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tag"); + + b.HasKey("Id"); + + b.HasIndex("Tag") + .IsUnique(); + + b.ToTable("data"); + }); + + modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("AddTime") + .HasColumnType("TEXT") + .HasColumnName("add_time"); + + b.Property("OperatorId") + .HasColumnType("INTEGER") + .HasColumnName("operator_id"); + + b.Property("Order") + .HasColumnType("INTEGER") + .HasColumnName("order"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Key") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("key"); + + b.HasKey("Id"); + + b.ToTable("jwt_token"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("TEXT") + .HasColumnName("color"); + + b.Property("CreateTime") + .HasColumnType("TEXT") + .HasColumnName("create_time"); + + b.Property("CurrentPostLocalId") + .HasColumnType("INTEGER") + .HasColumnName("current_post_local_id"); + + b.Property("Description") + .HasColumnType("TEXT") + .HasColumnName("description"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("NameLastModified") + .HasColumnType("TEXT") + .HasColumnName("name_last_modified"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("owner"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Visibility") + .HasColumnType("INTEGER") + .HasColumnName("visibility"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("timelines"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("TimelineId") + .HasColumnType("INTEGER") + .HasColumnName("timeline"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("INTEGER") + .HasColumnName("author"); + + b.Property("Color") + .HasColumnType("TEXT") + .HasColumnName("color"); + + b.Property("Content") + .HasColumnType("TEXT") + .HasColumnName("content"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("content_type"); + + b.Property("ExtraContent") + .HasColumnType("TEXT") + .HasColumnName("extra_content"); + + b.Property("LastUpdated") + .HasColumnType("TEXT") + .HasColumnName("last_updated"); + + b.Property("LocalId") + .HasColumnType("INTEGER") + .HasColumnName("local_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("DataTag") + .HasColumnType("TEXT") + .HasColumnName("data_tag"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Type") + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("user"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("user_avatars"); + }); + + modelBuilder.Entity("Timeline.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("CreateTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("create_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("LastModified") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("last_modified") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("Nickname") + .HasColumnType("TEXT") + .HasColumnName("nickname"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("password"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.Property("UsernameChangeTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("username_change_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Permission") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("permission"); + + b.Property("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/20210204133241_AddColor.cs b/BackEnd/Timeline/Migrations/20210204133241_AddColor.cs new file mode 100644 index 00000000..96ad584c --- /dev/null +++ b/BackEnd/Timeline/Migrations/20210204133241_AddColor.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Timeline.Migrations +{ + public partial class AddColor : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "color", + table: "timelines", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "color", + table: "timeline_posts", + type: "TEXT", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "color", + table: "timelines"); + + migrationBuilder.DropColumn( + name: "color", + table: "timeline_posts"); + } + } +} diff --git a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs index 6b547a55..2e2b0d36 100644 --- a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs +++ b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs @@ -129,6 +129,10 @@ namespace Timeline.Migrations .HasColumnType("INTEGER") .HasColumnName("id"); + b.Property("Color") + .HasColumnType("TEXT") + .HasColumnName("color"); + b.Property("CreateTime") .HasColumnType("TEXT") .HasColumnName("create_time"); @@ -214,6 +218,10 @@ namespace Timeline.Migrations .HasColumnType("INTEGER") .HasColumnName("author"); + b.Property("Color") + .HasColumnType("TEXT") + .HasColumnName("color"); + b.Property("Content") .HasColumnType("TEXT") .HasColumnName("content"); -- cgit v1.2.3