aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2020-03-06 22:28:32 +0800
committercrupest <crupest@outlook.com>2020-03-06 22:28:32 +0800
commit968140e8aaba398e10585e978aff33d7b32e824a (patch)
treef7a5aee626f4771a0c75ddc67c0b13b67c472a10
parenteb9554b4fe78eaced12329e7fd7d8d62a58a1c6c (diff)
downloadtimeline-968140e8aaba398e10585e978aff33d7b32e824a.tar.gz
timeline-968140e8aaba398e10585e978aff33d7b32e824a.tar.bz2
timeline-968140e8aaba398e10585e978aff33d7b32e824a.zip
Init development of post image feature.
-rw-r--r--Timeline/Controllers/UserAvatarController.cs8
-rw-r--r--Timeline/Entities/TimelinePostEntity.cs3
-rw-r--r--Timeline/Models/Http/TimelineCommon.cs35
-rw-r--r--Timeline/Models/Http/TimelineController.cs11
-rw-r--r--Timeline/Resources/Services/Exception.Designer.cs84
-rw-r--r--Timeline/Resources/Services/Exception.resx30
-rw-r--r--Timeline/Services/AvatarFormatException.cs51
-rw-r--r--Timeline/Services/ImageException.cs54
-rw-r--r--Timeline/Services/ImageValidator.cs50
-rw-r--r--Timeline/Services/TimelineService.cs39
-rw-r--r--Timeline/Services/UserAvatarService.cs24
11 files changed, 248 insertions, 141 deletions
diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs
index 2dd279a8..f4f3db3e 100644
--- a/Timeline/Controllers/UserAvatarController.cs
+++ b/Timeline/Controllers/UserAvatarController.cs
@@ -126,14 +126,14 @@ namespace Timeline.Controllers
("Username", username), ("Mime Type", Request.ContentType)));
return Ok();
}
- catch (AvatarFormatException e)
+ catch (ImageException e)
{
_logger.LogInformation(e, Log.Format(LogPutUserBadFormat, ("Username", username)));
return BadRequest(e.Error switch
{
- AvatarFormatException.ErrorReason.CantDecode => ErrorResponse.UserAvatar.BadFormat_CantDecode(),
- AvatarFormatException.ErrorReason.UnmatchedFormat => ErrorResponse.UserAvatar.BadFormat_UnmatchedFormat(),
- AvatarFormatException.ErrorReason.BadSize => ErrorResponse.UserAvatar.BadFormat_BadSize(),
+ ImageException.ErrorReason.CantDecode => ErrorResponse.UserAvatar.BadFormat_CantDecode(),
+ ImageException.ErrorReason.UnmatchedFormat => ErrorResponse.UserAvatar.BadFormat_UnmatchedFormat(),
+ ImageException.ErrorReason.NotSquare => ErrorResponse.UserAvatar.BadFormat_BadSize(),
_ =>
throw new Exception(ExceptionUnknownAvatarFormatError)
});
diff --git a/Timeline/Entities/TimelinePostEntity.cs b/Timeline/Entities/TimelinePostEntity.cs
index 5805abe0..ca2703b3 100644
--- a/Timeline/Entities/TimelinePostEntity.cs
+++ b/Timeline/Entities/TimelinePostEntity.cs
@@ -25,6 +25,9 @@ namespace Timeline.Entities
[ForeignKey(nameof(AuthorId))]
public UserEntity Author { get; set; } = default!;
+ [Column("content_type"), Required]
+ public string ContentType { get; set; } = default!;
+
[Column("content")]
public string? Content { get; set; }
diff --git a/Timeline/Models/Http/TimelineCommon.cs b/Timeline/Models/Http/TimelineCommon.cs
index d0dfd837..3be4f895 100644
--- a/Timeline/Models/Http/TimelineCommon.cs
+++ b/Timeline/Models/Http/TimelineCommon.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
+using System.Text.Json.Serialization;
using Timeline.Controllers;
namespace Timeline.Models.Http
@@ -21,10 +22,42 @@ namespace Timeline.Models.Http
Private
}
+ public static class TimelinePostContentTypes
+ {
+ public const string Text = "text";
+ public const string Image = "image";
+ }
+
+ public interface ITimelinePostContent
+ {
+ public string Type { get; }
+ }
+
+ public class TextTimelinePostContent : ITimelinePostContent
+ {
+ public TextTimelinePostContent() { }
+ public TextTimelinePostContent(string text) { Text = text; }
+
+ public string Type { get; } = TimelinePostContentTypes.Text;
+ public string Text { get; set; } = "";
+ }
+
+ public class ImageTimelinePostContent : ITimelinePostContent
+ {
+ public ImageTimelinePostContent() { }
+ public ImageTimelinePostContent(string dataTag) { DataTag = dataTag; }
+
+ public string Type { get; } = TimelinePostContentTypes.Image;
+ [JsonIgnore]
+ public string DataTag { get; set; } = "";
+ public string? Url { get; set; } = null;
+ }
+
public class TimelinePostInfo
{
public long Id { get; set; }
- public string Content { get; set; } = default!;
+ // use object to make json serializer use the runtime type
+ public object Content { get; set; } = default!;
public DateTime Time { get; set; }
public UserInfo Author { get; set; } = default!;
public DateTime LastUpdated { get; set; } = default!;
diff --git a/Timeline/Models/Http/TimelineController.cs b/Timeline/Models/Http/TimelineController.cs
index 6d461bb9..ce5f3b98 100644
--- a/Timeline/Models/Http/TimelineController.cs
+++ b/Timeline/Models/Http/TimelineController.cs
@@ -4,10 +4,17 @@ using Timeline.Models.Validation;
namespace Timeline.Models.Http
{
+ public class TimelinePostCreateRequestContent
+ {
+ [Required]
+ public string Type { get; set; } = default!;
+ public string? Text { get; set; }
+ public string? Data { get; set; }
+ }
+
public class TimelinePostCreateRequest
{
- [Required(AllowEmptyStrings = true)]
- public string Content { get; set; } = default!;
+ public TimelinePostCreateRequestContent Content { get; set; } = default!;
public DateTime? Time { get; set; }
}
diff --git a/Timeline/Resources/Services/Exception.Designer.cs b/Timeline/Resources/Services/Exception.Designer.cs
index e6806873..0c721d92 100644
--- a/Timeline/Resources/Services/Exception.Designer.cs
+++ b/Timeline/Resources/Services/Exception.Designer.cs
@@ -61,128 +61,128 @@ namespace Timeline.Resources.Services {
}
/// <summary>
- /// Looks up a localized string similar to Avartar is of bad format because {0}..
+ /// Looks up a localized string similar to The password is wrong..
/// </summary>
- internal static string AvatarFormatException {
+ internal static string BadPasswordException {
get {
- return ResourceManager.GetString("AvatarFormatException", resourceCulture);
+ return ResourceManager.GetString("BadPasswordException", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to image is not a square, aka, width is not equal to height.
+ /// Looks up a localized string similar to A present resource conflicts with the given resource..
/// </summary>
- internal static string AvatarFormatExceptionBadSize {
+ internal static string ConflictException {
get {
- return ResourceManager.GetString("AvatarFormatExceptionBadSize", resourceCulture);
+ return ResourceManager.GetString("ConflictException", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to failed to decode image, see inner exception.
+ /// Looks up a localized string similar to The hashes password is of bad format. It might not be created by server..
/// </summary>
- internal static string AvatarFormatExceptionCantDecode {
+ internal static string HashedPasswordBadFromatException {
get {
- return ResourceManager.GetString("AvatarFormatExceptionCantDecode", resourceCulture);
+ return ResourceManager.GetString("HashedPasswordBadFromatException", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to unknown error.
+ /// Looks up a localized string similar to Not of valid base64 format. See inner exception..
/// </summary>
- internal static string AvatarFormatExceptionUnknownError {
+ internal static string HashedPasswordBadFromatExceptionNotBase64 {
get {
- return ResourceManager.GetString("AvatarFormatExceptionUnknownError", resourceCulture);
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotBase64", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to image&apos;s actual mime type is not the specified one.
+ /// Looks up a localized string similar to Decoded hashed password is of length 0..
/// </summary>
- internal static string AvatarFormatExceptionUnmatchedFormat {
+ internal static string HashedPasswordBadFromatExceptionNotLength0 {
get {
- return ResourceManager.GetString("AvatarFormatExceptionUnmatchedFormat", resourceCulture);
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotLength0", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to The password is wrong..
+ /// Looks up a localized string similar to See inner exception..
/// </summary>
- internal static string BadPasswordException {
+ internal static string HashedPasswordBadFromatExceptionNotOthers {
get {
- return ResourceManager.GetString("BadPasswordException", resourceCulture);
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotOthers", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to A present resource conflicts with the given resource..
+ /// Looks up a localized string similar to Salt length &lt; 128 bits..
/// </summary>
- internal static string ConflictException {
+ internal static string HashedPasswordBadFromatExceptionNotSaltTooShort {
get {
- return ResourceManager.GetString("ConflictException", resourceCulture);
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotSaltTooShort", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to The hashes password is of bad format. It might not be created by server..
+ /// Looks up a localized string similar to Subkey length &lt; 128 bits..
/// </summary>
- internal static string HashedPasswordBadFromatException {
+ internal static string HashedPasswordBadFromatExceptionNotSubkeyTooShort {
get {
- return ResourceManager.GetString("HashedPasswordBadFromatException", resourceCulture);
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotSubkeyTooShort", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to Not of valid base64 format. See inner exception..
+ /// Looks up a localized string similar to Unknown format marker..
/// </summary>
- internal static string HashedPasswordBadFromatExceptionNotBase64 {
+ internal static string HashedPasswordBadFromatExceptionNotUnknownMarker {
get {
- return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotBase64", resourceCulture);
+ return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotUnknownMarker", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to Decoded hashed password is of length 0..
+ /// Looks up a localized string similar to Image is in valid because {0}..
/// </summary>
- internal static string HashedPasswordBadFromatExceptionNotLength0 {
+ internal static string ImageException {
get {
- return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotLength0", resourceCulture);
+ return ResourceManager.GetString("ImageException", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to See inner exception..
+ /// Looks up a localized string similar to image is not a square, aka, width is not equal to height.
/// </summary>
- internal static string HashedPasswordBadFromatExceptionNotOthers {
+ internal static string ImageExceptionBadSize {
get {
- return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotOthers", resourceCulture);
+ return ResourceManager.GetString("ImageExceptionBadSize", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to Salt length &lt; 128 bits..
+ /// Looks up a localized string similar to failed to decode image, see inner exception.
/// </summary>
- internal static string HashedPasswordBadFromatExceptionNotSaltTooShort {
+ internal static string ImageExceptionCantDecode {
get {
- return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotSaltTooShort", resourceCulture);
+ return ResourceManager.GetString("ImageExceptionCantDecode", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to Subkey length &lt; 128 bits..
+ /// Looks up a localized string similar to unknown error.
/// </summary>
- internal static string HashedPasswordBadFromatExceptionNotSubkeyTooShort {
+ internal static string ImageExceptionUnknownError {
get {
- return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotSubkeyTooShort", resourceCulture);
+ return ResourceManager.GetString("ImageExceptionUnknownError", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to Unknown format marker..
+ /// Looks up a localized string similar to image&apos;s actual mime type is not the specified one.
/// </summary>
- internal static string HashedPasswordBadFromatExceptionNotUnknownMarker {
+ internal static string ImageExceptionUnmatchedFormat {
get {
- return ResourceManager.GetString("HashedPasswordBadFromatExceptionNotUnknownMarker", resourceCulture);
+ return ResourceManager.GetString("ImageExceptionUnmatchedFormat", resourceCulture);
}
}
diff --git a/Timeline/Resources/Services/Exception.resx b/Timeline/Resources/Services/Exception.resx
index 11ae5f27..660e5b3d 100644
--- a/Timeline/Resources/Services/Exception.resx
+++ b/Timeline/Resources/Services/Exception.resx
@@ -117,21 +117,6 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
- <data name="AvatarFormatException" xml:space="preserve">
- <value>Avartar is of bad format because {0}.</value>
- </data>
- <data name="AvatarFormatExceptionBadSize" xml:space="preserve">
- <value>image is not a square, aka, width is not equal to height</value>
- </data>
- <data name="AvatarFormatExceptionCantDecode" xml:space="preserve">
- <value>failed to decode image, see inner exception</value>
- </data>
- <data name="AvatarFormatExceptionUnknownError" xml:space="preserve">
- <value>unknown error</value>
- </data>
- <data name="AvatarFormatExceptionUnmatchedFormat" xml:space="preserve">
- <value>image's actual mime type is not the specified one</value>
- </data>
<data name="BadPasswordException" xml:space="preserve">
<value>The password is wrong.</value>
</data>
@@ -159,6 +144,21 @@
<data name="HashedPasswordBadFromatExceptionNotUnknownMarker" xml:space="preserve">
<value>Unknown format marker.</value>
</data>
+ <data name="ImageException" xml:space="preserve">
+ <value>Image is in valid because {0}.</value>
+ </data>
+ <data name="ImageExceptionBadSize" xml:space="preserve">
+ <value>image is not a square, aka, width is not equal to height</value>
+ </data>
+ <data name="ImageExceptionCantDecode" xml:space="preserve">
+ <value>failed to decode image, see inner exception</value>
+ </data>
+ <data name="ImageExceptionUnknownError" xml:space="preserve">
+ <value>unknown error</value>
+ </data>
+ <data name="ImageExceptionUnmatchedFormat" xml:space="preserve">
+ <value>image's actual mime type is not the specified one</value>
+ </data>
<data name="JwtUserTokenBadFormatException" xml:space="preserve">
<value>The token didn't pass verification because {0}.</value>
</data>
diff --git a/Timeline/Services/AvatarFormatException.cs b/Timeline/Services/AvatarFormatException.cs
deleted file mode 100644
index 788eabb2..00000000
--- a/Timeline/Services/AvatarFormatException.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-using System.Globalization;
-
-namespace Timeline.Services
-{
- /// <summary>
- /// Thrown when avatar is of bad format.
- /// </summary>
- [Serializable]
- public class AvatarFormatException : Exception
- {
- public enum ErrorReason
- {
- /// <summary>
- /// Decoding image failed.
- /// </summary>
- CantDecode,
- /// <summary>
- /// Decoding succeeded but the real type is not the specified type.
- /// </summary>
- UnmatchedFormat,
- /// <summary>
- /// Image is not a square.
- /// </summary>
- BadSize
- }
-
- public AvatarFormatException() : base(MakeMessage(null)) { }
- public AvatarFormatException(string message) : base(message) { }
- public AvatarFormatException(string message, Exception inner) : base(message, inner) { }
-
- public AvatarFormatException(Avatar avatar, ErrorReason error) : base(MakeMessage(error)) { Avatar = avatar; Error = error; }
- public AvatarFormatException(Avatar avatar, ErrorReason error, Exception inner) : base(MakeMessage(error), inner) { Avatar = avatar; Error = error; }
-
- protected AvatarFormatException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
-
- private static string MakeMessage(ErrorReason? reason) =>
- string.Format(CultureInfo.InvariantCulture, Resources.Services.Exception.AvatarFormatException, reason switch
- {
- ErrorReason.CantDecode => Resources.Services.Exception.AvatarFormatExceptionCantDecode,
- ErrorReason.UnmatchedFormat => Resources.Services.Exception.AvatarFormatExceptionUnmatchedFormat,
- ErrorReason.BadSize => Resources.Services.Exception.AvatarFormatExceptionBadSize,
- _ => Resources.Services.Exception.AvatarFormatExceptionUnknownError
- });
-
- public ErrorReason? Error { get; set; }
- public Avatar? Avatar { get; set; }
- }
-}
diff --git a/Timeline/Services/ImageException.cs b/Timeline/Services/ImageException.cs
new file mode 100644
index 00000000..c6126aa3
--- /dev/null
+++ b/Timeline/Services/ImageException.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Globalization;
+
+namespace Timeline.Services
+{
+ [Serializable]
+ public class ImageException : Exception
+ {
+ public enum ErrorReason
+ {
+ /// <summary>
+ /// Decoding image failed.
+ /// </summary>
+ CantDecode,
+ /// <summary>
+ /// Decoding succeeded but the real type is not the specified type.
+ /// </summary>
+ UnmatchedFormat,
+ /// <summary>
+ /// Image is not a square.
+ /// </summary>
+ NotSquare
+ }
+
+ public ImageException() : base(MakeMessage(null)) { }
+ public ImageException(string message) : base(message) { }
+ public ImageException(string message, Exception inner) : base(message, inner) { }
+
+ public ImageException(ErrorReason error, byte[]? data = null, string? requestType = null, string? realType = null) : base(MakeMessage(error)) { Error = error; ImageData = data; RequestType = requestType; RealType = realType; }
+ public ImageException(Exception inner, ErrorReason error, byte[]? data = null, string? requestType = null, string? realType = null) : base(MakeMessage(error), inner) { Error = error; ImageData = data; RequestType = requestType; RealType = realType; }
+
+ protected ImageException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
+
+ private static string MakeMessage(ErrorReason? reason) =>
+ string.Format(CultureInfo.InvariantCulture, Resources.Services.Exception.ImageException, reason switch
+ {
+ ErrorReason.CantDecode => Resources.Services.Exception.ImageExceptionCantDecode,
+ ErrorReason.UnmatchedFormat => Resources.Services.Exception.ImageExceptionUnmatchedFormat,
+ ErrorReason.NotSquare => Resources.Services.Exception.ImageExceptionBadSize,
+ _ => Resources.Services.Exception.ImageExceptionUnknownError
+ });
+
+ public ErrorReason? Error { get; }
+#pragma warning disable CA1819 // Properties should not return arrays
+ public byte[]? ImageData { get; }
+#pragma warning restore CA1819 // Properties should not return arrays
+ public string? RequestType { get; }
+
+ // This field will be null if decoding failed.
+ public string? RealType { get; }
+ }
+}
diff --git a/Timeline/Services/ImageValidator.cs b/Timeline/Services/ImageValidator.cs
new file mode 100644
index 00000000..897a37b8
--- /dev/null
+++ b/Timeline/Services/ImageValidator.cs
@@ -0,0 +1,50 @@
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Timeline.Services
+{
+ public class ImageValidator
+ {
+ 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>
+ /// <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)
+ {
+ if (data == null)
+ throw new ArgumentNullException(nameof(data));
+
+ var format = await Task.Run(() =>
+ {
+ try
+ {
+ 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)
+ throw new ImageException(ImageException.ErrorReason.NotSquare, data, requestType, format.DefaultMimeType);
+ return format;
+ }
+ catch (UnknownImageFormatException e)
+ {
+ throw new ImageException(e, ImageException.ErrorReason.CantDecode, data, requestType, null);
+ }
+ });
+ return format;
+ }
+ }
+}
diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs
index 379ec8f5..d999587a 100644
--- a/Timeline/Services/TimelineService.cs
+++ b/Timeline/Services/TimelineService.cs
@@ -90,14 +90,14 @@ namespace Timeline.Services
Task<List<TimelinePostInfo>> GetPosts(string name);
/// <summary>
- /// Create a new post in timeline.
+ /// Create a new text post in timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <ssee cref="IBaseTimelineService"/>.</param>
/// <param name="authorId">The author's id.</param>
- /// <param name="content">The content.</param>
+ /// <param name="text">The content text.</param>
/// <param name="time">The time of the post. If null, then use current time.</param>
/// <returns>The info of the created post.</returns>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="content"/> is null.</exception>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="text"/> 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="TimelineNotExistException">
/// Thrown when timeline does not exist.
@@ -106,7 +106,27 @@ namespace Timeline.Services
/// and the inner exception should be a <see cref="UserNotExistException"/>.
/// </exception>
/// <exception cref="UserNotExistException">Thrown if user with <paramref name="authorId"/> does not exist.</exception>
- Task<TimelinePostInfo> CreatePost(string name, long authorId, string content, DateTime? time);
+ Task<TimelinePostInfo> CreateTextPost(string name, long authorId, string text, DateTime? time);
+
+ /// <summary>
+ /// Create a new image post in timeline.
+ /// </summary>
+ /// <param name="name">Username or the timeline name. See remarks of <ssee cref="IBaseTimelineService"/>.</param>
+ /// <param name="authorId">The author's id.</param>
+ /// <param name="data">The image data.</param>
+ /// <param name="time">The time of the post. If null, then use current time.</param>
+ /// <returns>The info of the created post.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="text"/> 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="TimelineNotExistException">
+ /// Thrown when timeline does not exist.
+ /// For normal timeline, it means the name does not exist.
+ /// For personal timeline, it means the user of that username does not exist
+ /// and the inner exception should be a <see cref="UserNotExistException"/>.
+ /// </exception>
+ /// <exception cref="UserNotExistException">Thrown if user with <paramref name="authorId"/> does not exist.</exception>
+ /// <exception cref="ImageException">Thrown if data is not a image. Validated by <see cref="ImageValidator"/>.</exception>
+ Task<TimelinePostInfo> CreateImagePost(string name, long authorId, byte[] data, DateTime? time);
/// <summary>
/// Delete a post
@@ -398,12 +418,12 @@ namespace Timeline.Services
return posts;
}
- public async Task<TimelinePostInfo> CreatePost(string name, long authorId, string content, DateTime? time)
+ public async Task<TimelinePostInfo> CreateTextPost(string name, long authorId, string text, DateTime? time)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
- if (content == null)
- throw new ArgumentNullException(nameof(content));
+ if (text == null)
+ throw new ArgumentNullException(nameof(text));
var timelineId = await FindTimelineId(name);
var timelineEntity = await Database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
@@ -418,7 +438,8 @@ namespace Timeline.Services
var postEntity = new TimelinePostEntity
{
LocalId = timelineEntity.CurrentPostLocalId,
- Content = content,
+ ContentType = TimelinePostContentTypes.Text,
+ Content = text,
AuthorId = authorId,
TimelineId = timelineId,
Time = finalTime,
@@ -430,7 +451,7 @@ namespace Timeline.Services
return new TimelinePostInfo
{
Id = postEntity.LocalId,
- Content = content,
+ Content = text,
Author = author,
Time = finalTime,
LastUpdated = currentTime
diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs
index 52d079a3..27922bab 100644
--- a/Timeline/Services/UserAvatarService.cs
+++ b/Timeline/Services/UserAvatarService.cs
@@ -53,7 +53,7 @@ namespace Timeline.Services
/// Validate a avatar's format and size info.
/// </summary>
/// <param name="avatar">The avatar to validate.</param>
- /// <exception cref="AvatarFormatException">Thrown when validation failed.</exception>
+ /// <exception cref="ImageException">Thrown when validation failed.</exception>
Task Validate(Avatar avatar);
}
@@ -79,7 +79,7 @@ namespace Timeline.Services
/// <param name="id">The id of the user to set avatar for.</param>
/// <param name="avatar">The avatar. Can be null to delete the saved avatar.</param>
/// <exception cref="ArgumentException">Thrown if any field in <paramref name="avatar"/> is null when <paramref name="avatar"/> is not null.</exception>
- /// <exception cref="AvatarFormatException">Thrown if avatar is of bad format.</exception>
+ /// <exception cref="ImageException">Thrown if avatar is of bad format.</exception>
Task SetAvatar(long id, Avatar? avatar);
}
@@ -134,23 +134,13 @@ namespace Timeline.Services
public class UserAvatarValidator : IUserAvatarValidator
{
+ private readonly ImageValidator _innerValidator = new ImageValidator(true);
+
public Task Validate(Avatar avatar)
{
- return Task.Run(() =>
- {
- try
- {
- using var image = Image.Load(avatar.Data, out IImageFormat format);
- if (!format.MimeTypes.Contains(avatar.Type))
- throw new AvatarFormatException(avatar, AvatarFormatException.ErrorReason.UnmatchedFormat);
- if (image.Width != image.Height)
- throw new AvatarFormatException(avatar, AvatarFormatException.ErrorReason.BadSize);
- }
- catch (UnknownImageFormatException e)
- {
- throw new AvatarFormatException(avatar, AvatarFormatException.ErrorReason.CantDecode, e);
- }
- });
+ if (avatar == null)
+ throw new ArgumentNullException(nameof(avatar));
+ return _innerValidator.Validate(avatar.Data, avatar.Type);
}
}