From 6488df7d9e7b1c3cc9fbceb0fa25a9770988a7ca Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 7 Mar 2020 22:02:31 +0800 Subject: ... --- Timeline/Entities/TimelinePostEntity.cs | 3 ++ Timeline/Services/ImageValidator.cs | 23 +++++---- Timeline/Services/TimelineService.cs | 86 ++++++++++++++++++++++++++++++--- Timeline/Services/UserAvatarService.cs | 35 ++------------ Timeline/Startup.cs | 2 + 5 files changed, 103 insertions(+), 46 deletions(-) (limited to 'Timeline') diff --git a/Timeline/Entities/TimelinePostEntity.cs b/Timeline/Entities/TimelinePostEntity.cs index ca2703b3..24bfc7a3 100644 --- a/Timeline/Entities/TimelinePostEntity.cs +++ b/Timeline/Entities/TimelinePostEntity.cs @@ -31,6 +31,9 @@ namespace Timeline.Entities [Column("content")] public string? Content { get; set; } + [Column("extra_content")] + public string? ExtraContent { get; set; } + [Column("time")] public DateTime Time { get; set; } diff --git a/Timeline/Services/ImageValidator.cs b/Timeline/Services/ImageValidator.cs index 897a37b8..c331d912 100644 --- a/Timeline/Services/ImageValidator.cs +++ b/Timeline/Services/ImageValidator.cs @@ -6,24 +6,27 @@ using System.Threading.Tasks; namespace Timeline.Services { - public class ImageValidator + public interface IImageValidator { - private readonly bool _requireSquare; - - public ImageValidator(bool requireSquare = false) - { - _requireSquare = requireSquare; - } - /// /// Validate a image data. /// /// The data of the image. Can't be null. /// If not null, the real image format will be check against the requested format and throw if not match. If null, then do not check. + /// If true, image must be square. /// The format. /// Thrown when is null. /// Thrown when image data can't be decoded or real type does not match request type or image is not square when required. - public async Task Validate(byte[] data, string? requestType = null) + Task Validate(byte[] data, string? requestType = null, bool square = false); + } + + public class ImageValidator : IImageValidator + { + public ImageValidator() + { + } + + public async Task Validate(byte[] data, string? requestType = null, bool square = false) { if (data == null) throw new ArgumentNullException(nameof(data)); @@ -35,7 +38,7 @@ namespace Timeline.Services using var image = Image.Load(data, out IImageFormat format); if (requestType != null && !format.MimeTypes.Contains(requestType)) throw new ImageException(ImageException.ErrorReason.UnmatchedFormat, data, requestType, format.DefaultMimeType); - if (_requireSquare && image.Width != image.Height) + if (square && image.Width != image.Height) throw new ImageException(ImageException.ErrorReason.NotSquare, data, requestType, format.DefaultMimeType); return format; } diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index d999587a..edd0a0ab 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -32,6 +32,14 @@ namespace Timeline.Services public long UserId { get; set; } } + public class DataWithType + { +#pragma warning disable CA1819 // Properties should not return arrays + public byte[] Data { get; set; } = default!; +#pragma warning restore CA1819 // Properties should not return arrays + public string Type { get; set; } = default!; + } + /// /// This define the common interface of both personal timeline /// and normal timeline. @@ -89,6 +97,21 @@ namespace Timeline.Services /// Task> GetPosts(string name); + /// + /// Get the data of a post. + /// + /// Username or the timeline name. See remarks of . + /// The id of the post. + /// The data and its type. + /// Thrown when is null. + /// Thrown when is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service). + /// Thrown when post of does not exist. + /// Thrown when post has no data. See remarks. + /// + /// Use this method to retrieve the image of image post. + /// + Task GetPostData(string name, long postId); + /// /// Create a new text post in timeline. /// @@ -307,10 +330,12 @@ namespace Timeline.Services public abstract class BaseTimelineService : IBaseTimelineService { - protected BaseTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IUserService userService, IMapper mapper, IClock clock) + protected BaseTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IImageValidator imageValidator, IDataManager dataManager, IUserService userService, IMapper mapper, IClock clock) { Clock = clock; Database = database; + ImageValidator = imageValidator; + DataManager = dataManager; UserService = userService; Mapper = mapper; } @@ -321,6 +346,8 @@ namespace Timeline.Services protected DatabaseContext Database { get; } + protected IImageValidator ImageValidator { get; } + protected IDataManager DataManager { get; } protected IUserService UserService { get; } protected IMapper Mapper { get; } @@ -451,7 +478,54 @@ namespace Timeline.Services return new TimelinePostInfo { Id = postEntity.LocalId, - Content = text, + Content = new TextTimelinePostContent(text), + Author = author, + Time = finalTime, + LastUpdated = currentTime + }; + } + + public async Task CreateImagePost(string name, long authorId, byte[] data, DateTime? time) + { + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (data == null) + throw new ArgumentNullException(nameof(data)); + + var timelineId = await FindTimelineId(name); + var timelineEntity = await Database.Timelines.Where(t => t.Id == timelineId).SingleAsync(); + + var author = Mapper.Map(await UserService.GetUserById(authorId)); + + var imageFormat = await ImageValidator.Validate(data); + + var imageFormatText = imageFormat.DefaultMimeType; + + var tag = await DataManager.RetainEntry(data); + + var currentTime = Clock.GetCurrentTime(); + var finalTime = time ?? currentTime; + + timelineEntity.CurrentPostLocalId += 1; + + var postEntity = new TimelinePostEntity + { + 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(); + + return new TimelinePostInfo + { + Id = postEntity.LocalId, + Content = new ImageTimelinePostContent(tag), Author = author, Time = finalTime, LastUpdated = currentTime @@ -658,8 +732,8 @@ namespace Timeline.Services } } - public TimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IUserService userService, IMapper mapper, IClock clock) - : base(loggerFactory, database, userService, mapper, clock) + public TimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IImageValidator imageValidator, IDataManager dataManager, IUserService userService, IMapper mapper, IClock clock) + : base(loggerFactory, database, imageValidator, dataManager, userService, mapper, clock) { } @@ -788,8 +862,8 @@ namespace Timeline.Services public class PersonalTimelineService : BaseTimelineService, IPersonalTimelineService { - public PersonalTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IUserService userService, IMapper mapper, IClock clock) - : base(loggerFactory, database, userService, mapper, clock) + public PersonalTimelineService(ILoggerFactory loggerFactory, DatabaseContext database, IImageValidator imageValidator, IDataManager dataManager, IUserService userService, IMapper mapper, IClock clock) + : base(loggerFactory, database, imageValidator, dataManager, userService, mapper, clock) { } diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index 27922bab..1b1be698 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -1,10 +1,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats; using System; using System.IO; using System.Linq; @@ -47,16 +44,6 @@ namespace Timeline.Services Task GetDefaultAvatar(); } - public interface IUserAvatarValidator - { - /// - /// Validate a avatar's format and size info. - /// - /// The avatar to validate. - /// Thrown when validation failed. - Task Validate(Avatar avatar); - } - public interface IUserAvatarService { /// @@ -132,18 +119,6 @@ namespace Timeline.Services } } - public class UserAvatarValidator : IUserAvatarValidator - { - private readonly ImageValidator _innerValidator = new ImageValidator(true); - - public Task Validate(Avatar avatar) - { - if (avatar == null) - throw new ArgumentNullException(nameof(avatar)); - return _innerValidator.Validate(avatar.Data, avatar.Type); - } - } - public class UserAvatarService : IUserAvatarService { @@ -152,7 +127,8 @@ namespace Timeline.Services private readonly DatabaseContext _database; private readonly IDefaultUserAvatarProvider _defaultUserAvatarProvider; - private readonly IUserAvatarValidator _avatarValidator; + + private readonly IImageValidator _imageValidator; private readonly IDataManager _dataManager; @@ -162,14 +138,14 @@ namespace Timeline.Services ILogger logger, DatabaseContext database, IDefaultUserAvatarProvider defaultUserAvatarProvider, - IUserAvatarValidator avatarValidator, + IImageValidator imageValidator, IDataManager dataManager, IClock clock) { _logger = logger; _database = database; _defaultUserAvatarProvider = defaultUserAvatarProvider; - _avatarValidator = avatarValidator; + _imageValidator = imageValidator; _dataManager = dataManager; _clock = clock; } @@ -247,7 +223,7 @@ namespace Timeline.Services } else { - await _avatarValidator.Validate(avatar); + await _imageValidator.Validate(avatar.Data, avatar.Type, true); var tag = await _dataManager.RetainEntry(avatar.Data); var oldTag = avatarEntity?.DataTag; var create = avatarEntity == null; @@ -278,7 +254,6 @@ namespace Timeline.Services { services.AddScoped(); services.AddScoped(); - services.AddTransient(); } } } diff --git a/Timeline/Startup.cs b/Timeline/Startup.cs index 263e6b7a..873d2d3b 100644 --- a/Timeline/Startup.cs +++ b/Timeline/Startup.cs @@ -97,6 +97,8 @@ namespace Timeline services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddUserAvatarService(); services.AddScoped(); -- cgit v1.2.3