From b9dc0062241f4dcf5221808d97a7e4c337a8b6cc Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 2 Feb 2020 14:35:30 +0800 Subject: Add timeline service. --- Timeline/Entities/TimelineEntity.cs | 4 +- Timeline/Models/Validation/NameValidator.cs | 33 ++++++ .../Models/Validation/TimelineNameValidator.cs | 19 +++ Timeline/Models/Validation/UsernameValidator.cs | 28 +---- .../Models/Validation/NameValidator.Designer.cs | 90 ++++++++++++++ .../Resources/Models/Validation/NameValidator.resx | 129 +++++++++++++++++++++ .../Validation/UsernameValidator.Designer.cs | 90 -------------- .../Models/Validation/UsernameValidator.resx | 129 --------------------- .../Resources/Services/TimelineService.Designer.cs | 18 +++ Timeline/Resources/Services/TimelineService.resx | 6 + Timeline/Services/TimelineService.cs | 96 +++++++++++++-- Timeline/Timeline.csproj | 8 +- 12 files changed, 388 insertions(+), 262 deletions(-) create mode 100644 Timeline/Models/Validation/NameValidator.cs create mode 100644 Timeline/Models/Validation/TimelineNameValidator.cs create mode 100644 Timeline/Resources/Models/Validation/NameValidator.Designer.cs create mode 100644 Timeline/Resources/Models/Validation/NameValidator.resx delete mode 100644 Timeline/Resources/Models/Validation/UsernameValidator.Designer.cs delete mode 100644 Timeline/Resources/Models/Validation/UsernameValidator.resx diff --git a/Timeline/Entities/TimelineEntity.cs b/Timeline/Entities/TimelineEntity.cs index c50fe6dd..0b252bab 100644 --- a/Timeline/Entities/TimelineEntity.cs +++ b/Timeline/Entities/TimelineEntity.cs @@ -6,7 +6,8 @@ using Timeline.Models.Http; namespace Timeline.Entities { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is entity object.")] +#pragma warning disable CA2227 // Collection properties should be read only + // TODO: Create index for this table. [Table("timelines")] public class TimelineEntity { @@ -38,4 +39,5 @@ namespace Timeline.Entities public List Posts { get; set; } = default!; } +#pragma warning restore CA2227 // Collection properties should be read only } diff --git a/Timeline/Models/Validation/NameValidator.cs b/Timeline/Models/Validation/NameValidator.cs new file mode 100644 index 00000000..8db10ebd --- /dev/null +++ b/Timeline/Models/Validation/NameValidator.cs @@ -0,0 +1,33 @@ +using System.Linq; +using static Timeline.Resources.Models.Validation.NameValidator; + +namespace Timeline.Models.Validation +{ + public class NameValidator : Validator + { + public const int MaxLength = 26; + + protected override (bool, string) DoValidate(string value) + { + if (value.Length == 0) + { + return (false, MessageEmptyString); + } + + if (value.Length > 26) + { + return (false, MessageTooLong); + } + + foreach ((char c, int i) in value.Select((c, i) => (c, i))) + { + if (!(char.IsLetterOrDigit(c) || c == '-' || c == '_')) + { + return (false, MessageInvalidChar); + } + } + + return (true, GetSuccessMessage()); + } + } +} diff --git a/Timeline/Models/Validation/TimelineNameValidator.cs b/Timeline/Models/Validation/TimelineNameValidator.cs new file mode 100644 index 00000000..f1ab54e8 --- /dev/null +++ b/Timeline/Models/Validation/TimelineNameValidator.cs @@ -0,0 +1,19 @@ +using System; + +namespace Timeline.Models.Validation +{ + public class TimelineNameValidator : NameValidator + { + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, + AllowMultiple = false)] + public class TimelineNameAttribute : ValidateWithAttribute + { + public TimelineNameAttribute() + : base(typeof(TimelineNameValidator)) + { + + } + } +} diff --git a/Timeline/Models/Validation/UsernameValidator.cs b/Timeline/Models/Validation/UsernameValidator.cs index d8f3bdc0..87bbf85f 100644 --- a/Timeline/Models/Validation/UsernameValidator.cs +++ b/Timeline/Models/Validation/UsernameValidator.cs @@ -1,35 +1,9 @@ using System; -using System.Linq; -using static Timeline.Resources.Models.Validation.UsernameValidator; namespace Timeline.Models.Validation { - public class UsernameValidator : Validator + public class UsernameValidator : NameValidator { - public const int MaxLength = 26; - - protected override (bool, string) DoValidate(string value) - { - if (value.Length == 0) - { - return (false, MessageEmptyString); - } - - if (value.Length > 26) - { - return (false, MessageTooLong); - } - - foreach ((char c, int i) in value.Select((c, i) => (c, i))) - { - if (!(char.IsLetterOrDigit(c) || c == '-' || c == '_')) - { - return (false, MessageInvalidChar); - } - } - - return (true, GetSuccessMessage()); - } } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, diff --git a/Timeline/Resources/Models/Validation/NameValidator.Designer.cs b/Timeline/Resources/Models/Validation/NameValidator.Designer.cs new file mode 100644 index 00000000..5b869226 --- /dev/null +++ b/Timeline/Resources/Models/Validation/NameValidator.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Timeline.Resources.Models.Validation { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class NameValidator { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal NameValidator() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Validation.NameValidator", typeof(NameValidator).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to An empty string is not allowed.. + /// + internal static string MessageEmptyString { + get { + return ResourceManager.GetString("MessageEmptyString", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid character, only alphabet, digit, underscore and hyphen are allowed.. + /// + internal static string MessageInvalidChar { + get { + return ResourceManager.GetString("MessageInvalidChar", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Too long, more than 26 characters is not premitted.. + /// + internal static string MessageTooLong { + get { + return ResourceManager.GetString("MessageTooLong", resourceCulture); + } + } + } +} diff --git a/Timeline/Resources/Models/Validation/NameValidator.resx b/Timeline/Resources/Models/Validation/NameValidator.resx new file mode 100644 index 00000000..08a814d0 --- /dev/null +++ b/Timeline/Resources/Models/Validation/NameValidator.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + An empty string is not allowed. + + + Invalid character, only alphabet, digit, underscore and hyphen are allowed. + + + Too long, more than 26 characters is not premitted. + + \ No newline at end of file diff --git a/Timeline/Resources/Models/Validation/UsernameValidator.Designer.cs b/Timeline/Resources/Models/Validation/UsernameValidator.Designer.cs deleted file mode 100644 index ac925504..00000000 --- a/Timeline/Resources/Models/Validation/UsernameValidator.Designer.cs +++ /dev/null @@ -1,90 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Timeline.Resources.Models.Validation { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class UsernameValidator { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal UsernameValidator() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Models.Validation.UsernameValidator", typeof(UsernameValidator).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to An empty string is not allowed.. - /// - internal static string MessageEmptyString { - get { - return ResourceManager.GetString("MessageEmptyString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invalid character, only alphabet, digit, underscore and hyphen are allowed.. - /// - internal static string MessageInvalidChar { - get { - return ResourceManager.GetString("MessageInvalidChar", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Too long, more than 26 characters is not premitted.. - /// - internal static string MessageTooLong { - get { - return ResourceManager.GetString("MessageTooLong", resourceCulture); - } - } - } -} diff --git a/Timeline/Resources/Models/Validation/UsernameValidator.resx b/Timeline/Resources/Models/Validation/UsernameValidator.resx deleted file mode 100644 index 08a814d0..00000000 --- a/Timeline/Resources/Models/Validation/UsernameValidator.resx +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - An empty string is not allowed. - - - Invalid character, only alphabet, digit, underscore and hyphen are allowed. - - - Too long, more than 26 characters is not premitted. - - \ No newline at end of file diff --git a/Timeline/Resources/Services/TimelineService.Designer.cs b/Timeline/Resources/Services/TimelineService.Designer.cs index 8212c252..3ee5959f 100644 --- a/Timeline/Resources/Services/TimelineService.Designer.cs +++ b/Timeline/Resources/Services/TimelineService.Designer.cs @@ -77,5 +77,23 @@ namespace Timeline.Resources.Services { return ResourceManager.GetString("ExceptionFindTimelineUsernameBadFormat", resourceCulture); } } + + /// + /// Looks up a localized string similar to The timeline name is of bad format because {0}.. + /// + internal static string ExceptionTimelineNameBadFormat { + get { + return ResourceManager.GetString("ExceptionTimelineNameBadFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The timeline with given name already exists.. + /// + internal static string ExceptionTimelineNameConflict { + get { + return ResourceManager.GetString("ExceptionTimelineNameConflict", resourceCulture); + } + } } } diff --git a/Timeline/Resources/Services/TimelineService.resx b/Timeline/Resources/Services/TimelineService.resx index 0429a2f8..e0d76c9a 100644 --- a/Timeline/Resources/Services/TimelineService.resx +++ b/Timeline/Resources/Services/TimelineService.resx @@ -123,4 +123,10 @@ The owner username of personal timeline is of bad format. + + The timeline name is of bad format because {0}. + + + The timeline with given name already exists. + \ No newline at end of file diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index a16237ca..b031297e 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -213,11 +213,12 @@ namespace Timeline.Services /// /// The name of the timeline. /// The id of owner of the timeline. - /// Thrown when or is null. - /// Thrown when timeline name is invalid. Currently it means it is an empty string. + /// The info of the new timeline. + /// Thrown when is null. + /// Thrown when timeline name is invalid. /// Thrown when the timeline already exists. /// Thrown when the owner user does not exist. - Task CreateTimeline(string name, long owner); + Task CreateTimeline(string name, long owner); } public interface IPersonalTimelineService : IBaseTimelineService @@ -245,6 +246,17 @@ namespace Timeline.Services protected IMapper Mapper { get; } + protected TimelineEntity CreateNewEntity(string? name, long owner) + { + return new TimelineEntity + { + Name = name, + OwnerId = owner, + Visibility = TimelineVisibility.Register, + CreateTime = Clock.GetCurrentTime() + }; + } + /// /// Find the timeline id by the name. /// For details, see remarks. @@ -537,6 +549,72 @@ namespace Timeline.Services } } + public class TimelineService : BaseTimelineService, ITimelineService + { + private readonly TimelineNameValidator _timelineNameValidator = new TimelineNameValidator(); + + private void ValidateTimelineName(string name, string paramName) + { + if (!_timelineNameValidator.Validate(name, out var message)) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionTimelineNameBadFormat, message), paramName); + } + } + + public TimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IUserService userService, IMapper mapper, IClock clock) + : base(loggerFactory, database, userService, mapper, clock) + { + + } + + protected override async Task FindTimelineId(string name) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + ValidateTimelineName(name, nameof(name)); + + var timelineEntity = await Database.Timelines.Where(t => t.Name == name).Select(t => new { t.Id }).SingleOrDefaultAsync(); + + if (timelineEntity == null) + { + throw new TimelineNotExistException(name); + } + else + { + return timelineEntity.Id; + } + } + + public async Task CreateTimeline(string name, long owner) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + + ValidateTimelineName(name, nameof(name)); + + var user = await UserService.GetUserById(owner); + + var conflict = await Database.Timelines.AnyAsync(t => t.Name == name); + + if (conflict) + throw new ConflictException(ExceptionTimelineNameConflict); + + var newEntity = CreateNewEntity(name, owner); + Database.Timelines.Add(newEntity); + await Database.SaveChangesAsync(); + + return new TimelineInfo + { + Name = name, + Description = "", + Owner = Mapper.Map(user), + Visibility = newEntity.Visibility, + Members = new List() + }; + } + } + public class PersonalTimelineService : BaseTimelineService, IPersonalTimelineService { public PersonalTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IUserService userService, IMapper mapper, IClock clock) @@ -547,6 +625,9 @@ namespace Timeline.Services protected override async Task FindTimelineId(string name) { + if (name == null) + throw new ArgumentNullException(nameof(name)); + long userId; try { @@ -569,14 +650,7 @@ namespace Timeline.Services } else { - var newTimelineEntity = new TimelineEntity - { - Name = null, - Description = null, - OwnerId = userId, - Visibility = TimelineVisibility.Register, - CreateTime = Clock.GetCurrentTime(), - }; + var newTimelineEntity = CreateNewEntity(null, userId); Database.Timelines.Add(newTimelineEntity); await Database.SaveChangesAsync(); diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj index 1a3a07cd..08999f82 100644 --- a/Timeline/Timeline.csproj +++ b/Timeline/Timeline.csproj @@ -89,10 +89,10 @@ True NicknameValidator.resx - + True True - UsernameValidator.resx + NameValidator.resx True @@ -163,9 +163,9 @@ ResXFileCodeGenerator NicknameValidator.Designer.cs - + ResXFileCodeGenerator - UsernameValidator.Designer.cs + NameValidator.Designer.cs ResXFileCodeGenerator -- cgit v1.2.3