diff options
Diffstat (limited to 'Timeline')
-rw-r--r-- | Timeline/Entities/TimelinePostEntity.cs | 3 | ||||
-rw-r--r-- | Timeline/Services/ImageValidator.cs | 23 | ||||
-rw-r--r-- | Timeline/Services/TimelineService.cs | 86 | ||||
-rw-r--r-- | Timeline/Services/UserAvatarService.cs | 35 | ||||
-rw-r--r-- | Timeline/Startup.cs | 2 |
5 files changed, 103 insertions, 46 deletions
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;
- }
-
/// <summary>
/// Validate a image data.
/// </summary>
/// <param name="data">The data of the image. Can't be null.</param>
/// <param name="requestType">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.</param>
+ /// <param name="square">If true, image must be square.</param>
/// <returns>The format.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="data"/> is null.</exception>
/// <exception cref="ImageException">Thrown when image data can't be decoded or real type does not match request type or image is not square when required.</exception>
- public async Task<IImageFormat> Validate(byte[] data, string? requestType = null)
+ Task<IImageFormat> Validate(byte[] data, string? requestType = null, bool square = false);
+ }
+
+ public class ImageValidator : IImageValidator
+ {
+ public ImageValidator()
+ {
+ }
+
+ public async Task<IImageFormat> 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!;
+ }
+
/// <summary>
/// This define the common interface of both personal timeline
/// and normal timeline.
@@ -90,6 +98,21 @@ namespace Timeline.Services Task<List<TimelinePostInfo>> GetPosts(string name);
/// <summary>
+ /// Get the data of a post.
+ /// </summary>
+ /// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
+ /// <param name="postId">The id of the post.</param>
+ /// <returns>The data and its type.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="name"/> is illegal. It is not a valid timeline name (for normal timeline service) or a valid username (for personal timeline service).</exception>
+ /// <exception cref="TimelinePostNotExistException">Thrown when post of <paramref name="postId"/> does not exist.</exception>
+ /// <exception cref="InvalidOperationException">Thrown when post has no data. See remarks.</exception>
+ /// <remarks>
+ /// Use this method to retrieve the image of image post.
+ /// </remarks>
+ Task<DataWithType> GetPostData(string name, long postId);
+
+ /// <summary>
/// Create a new text post in timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <ssee cref="IBaseTimelineService"/>.</param>
@@ -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<TimelinePostInfo> 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<UserInfo>(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<AvatarInfo> GetDefaultAvatar();
}
- public interface IUserAvatarValidator
- {
- /// <summary>
- /// Validate a avatar's format and size info.
- /// </summary>
- /// <param name="avatar">The avatar to validate.</param>
- /// <exception cref="ImageException">Thrown when validation failed.</exception>
- Task Validate(Avatar avatar);
- }
-
public interface IUserAvatarService
{
/// <summary>
@@ -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<UserAvatarService> 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<IUserAvatarService, UserAvatarService>();
services.AddScoped<IDefaultUserAvatarProvider, DefaultUserAvatarProvider>();
- services.AddTransient<IUserAvatarValidator, UserAvatarValidator>();
}
}
}
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<IETagGenerator, ETagGenerator>();
services.AddScoped<IDataManager, DataManager>();
+ services.AddScoped<IImageValidator, ImageValidator>();
+
services.AddUserAvatarService();
services.AddScoped<ITimelineService, TimelineService>();
|