diff options
-rw-r--r-- | Timeline/Controllers/PersonalTimelineController.cs | 141 | ||||
-rw-r--r-- | Timeline/Entities/TimelineEntity.cs | 10 | ||||
-rw-r--r-- | Timeline/Models/Http/Timeline.cs | 24 | ||||
-rw-r--r-- | Timeline/Resources/Controllers/TimelineController.Designer.cs | 90 | ||||
-rw-r--r-- | Timeline/Resources/Controllers/TimelineController.resx | 129 | ||||
-rw-r--r-- | Timeline/Resources/Controllers/TimelineController.zh.resx | 126 | ||||
-rw-r--r-- | Timeline/Services/TimelineService.cs | 64 | ||||
-rw-r--r-- | Timeline/Services/TimelineUserNotMemberException.cs | 15 | ||||
-rw-r--r-- | Timeline/Timeline.csproj | 9 |
9 files changed, 575 insertions, 33 deletions
diff --git a/Timeline/Controllers/PersonalTimelineController.cs b/Timeline/Controllers/PersonalTimelineController.cs new file mode 100644 index 00000000..1535a0b2 --- /dev/null +++ b/Timeline/Controllers/PersonalTimelineController.cs @@ -0,0 +1,141 @@ +using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Timeline.Auth;
+using Timeline.Entities;
+using Timeline.Filters;
+using Timeline.Models;
+using Timeline.Models.Http;
+using Timeline.Models.Validation;
+using Timeline.Services;
+using static Timeline.Resources.Controllers.TimelineController;
+
+namespace Timeline
+{
+ public static partial class ErrorCodes
+ {
+ public static partial class Http
+ {
+ public static class Timeline // ccc = 004
+ {
+ public const int PostsGetForbid = 10040101;
+ public const int PostsCreateForbid = 10040102;
+ }
+ }
+ }
+}
+
+namespace Timeline.Controllers
+{
+ [ApiController]
+ public class PersonalTimelineController : Controller
+ {
+ private readonly IPersonalTimelineService _service;
+
+ private bool IsAdmin()
+ {
+ if (User != null)
+ {
+ return User.IsAdministrator();
+ }
+ return false;
+ }
+
+ private string? GetAuthUsername()
+ {
+ if (User == null)
+ {
+ return null;
+ }
+ else
+ {
+ return User.Identity.Name;
+ }
+ }
+
+ public PersonalTimelineController(IPersonalTimelineService service)
+ {
+ _service = service;
+ }
+
+ [HttpGet("users/{username}/timeline")]
+ public async Task<ActionResult<BaseTimelineInfo>> TimelineGet([FromRoute][Username] string username)
+ {
+ return await _service.GetTimeline(username);
+ }
+
+ [HttpGet("users/{username}/timeline/posts")]
+ public async Task<ActionResult<IList<TimelinePostInfo>>> PostsGet([FromRoute][Username] string username)
+ {
+ if (!IsAdmin() && !await _service.HasReadPermission(username, GetAuthUsername()))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden,
+ new CommonResponse(ErrorCodes.Http.Timeline.PostsGetForbid, MessagePostsGetForbid));
+ }
+
+ return await _service.GetPosts(username);
+ }
+
+ [HttpPost("user/{username}/timeline/posts/create")]
+ [Authorize]
+ public async Task<ActionResult> PostsCreate([FromRoute][Username] string username, [FromBody] TimelinePostCreateRequest body)
+ {
+ if (!IsAdmin() && !await _service.IsMemberOf(username, GetAuthUsername()!))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden,
+ new CommonResponse(ErrorCodes.Http.Timeline.PostsCreateForbid, MessagePostsCreateForbid));
+ }
+
+ await _service.CreatePost(username, User.Identity.Name!, body.Content, body.Time);
+ return Ok();
+ }
+
+ [HttpPut("user/{username}/timeline/description")]
+ [Authorize]
+ [SelfOrAdmin]
+ public async Task<ActionResult> TimelinePutDescription([FromRoute][Username] string username, [FromBody] string body)
+ {
+ await _service.SetDescription(username, body);
+ return Ok();
+ }
+
+ private static TimelineVisibility StringToVisibility(string s)
+ {
+ if ("public".Equals(s, StringComparison.InvariantCultureIgnoreCase))
+ {
+ return TimelineVisibility.Public;
+ }
+ else if ("register".Equals(s, StringComparison.InvariantCultureIgnoreCase))
+ {
+ return TimelineVisibility.Register;
+ }
+ else if ("private".Equals(s, StringComparison.InvariantCultureIgnoreCase))
+ {
+ return TimelineVisibility.Private;
+ }
+ throw new ArgumentException(ExceptionStringToVisibility);
+ }
+
+ [HttpPut("user/{username}/timeline/visibility")]
+ [Authorize]
+ [SelfOrAdmin]
+ public async Task<ActionResult> TimelinePutVisibility([FromRoute][Username] string username, [FromBody][RegularExpression("public|register|private")] string body)
+ {
+ await _service.SetVisibility(username, StringToVisibility(body));
+ return Ok();
+ }
+
+ [HttpPost("user/{username}/timeline/members/change")]
+ [Authorize]
+ [SelfOrAdmin]
+ public async Task<ActionResult> TimelineMembersChange([FromRoute][Username] string username, [FromBody] TimelineMemberChangeRequest body)
+ {
+ //TODO!
+ }
+ }
+}
diff --git a/Timeline/Entities/TimelineEntity.cs b/Timeline/Entities/TimelineEntity.cs index f4c7045d..f5e22a54 100644 --- a/Timeline/Entities/TimelineEntity.cs +++ b/Timeline/Entities/TimelineEntity.cs @@ -7,7 +7,17 @@ namespace Timeline.Entities {
public enum TimelineVisibility
{
+ /// <summary>
+ /// All people including those without accounts.
+ /// </summary>
Public,
+ /// <summary>
+ /// Only people signed in.
+ /// </summary>
+ Register,
+ /// <summary>
+ /// Only member.
+ /// </summary>
Private
}
diff --git a/Timeline/Models/Http/Timeline.cs b/Timeline/Models/Http/Timeline.cs new file mode 100644 index 00000000..37de9e58 --- /dev/null +++ b/Timeline/Models/Http/Timeline.cs @@ -0,0 +1,24 @@ +using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Timeline.Models.Http
+{
+ public class TimelinePostCreateRequest
+ {
+ [Required(AllowEmptyStrings = false)]
+ public string Content { get; set; } = default!;
+
+ public DateTime? Time { get; set; }
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "This is a DTO class.")]
+ public class TimelineMemberChangeRequest
+ {
+ public List<string>? Add { get; set; }
+
+ public List<string>? Remove { get; set; }
+ }
+}
diff --git a/Timeline/Resources/Controllers/TimelineController.Designer.cs b/Timeline/Resources/Controllers/TimelineController.Designer.cs new file mode 100644 index 00000000..1e56f651 --- /dev/null +++ b/Timeline/Resources/Controllers/TimelineController.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Timeline.Resources.Controllers {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // 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 TimelineController {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal TimelineController() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [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.Controllers.TimelineController", typeof(TimelineController).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An unknown timeline visibility value. Can't convert it..
+ /// </summary>
+ internal static string ExceptionStringToVisibility {
+ get {
+ return ResourceManager.GetString("ExceptionStringToVisibility", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You have no permission to create posts in the timeline..
+ /// </summary>
+ internal static string MessagePostsCreateForbid {
+ get {
+ return ResourceManager.GetString("MessagePostsCreateForbid", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to You have no permission to read posts of the timeline..
+ /// </summary>
+ internal static string MessagePostsGetForbid {
+ get {
+ return ResourceManager.GetString("MessagePostsGetForbid", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Timeline/Resources/Controllers/TimelineController.resx b/Timeline/Resources/Controllers/TimelineController.resx new file mode 100644 index 00000000..420ac419 --- /dev/null +++ b/Timeline/Resources/Controllers/TimelineController.resx @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="ExceptionStringToVisibility" xml:space="preserve">
+ <value>An unknown timeline visibility value. Can't convert it.</value>
+ </data>
+ <data name="MessagePostsCreateForbid" xml:space="preserve">
+ <value>You have no permission to create posts in the timeline.</value>
+ </data>
+ <data name="MessagePostsGetForbid" xml:space="preserve">
+ <value>You have no permission to read posts of the timeline.</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Resources/Controllers/TimelineController.zh.resx b/Timeline/Resources/Controllers/TimelineController.zh.resx new file mode 100644 index 00000000..e22f44fa --- /dev/null +++ b/Timeline/Resources/Controllers/TimelineController.zh.resx @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="MessagePostsCreateForbid" xml:space="preserve">
+ <value>你没有权限在这个时间线中创建消息。</value>
+ </data>
+ <data name="MessagePostsGetForbid" xml:space="preserve">
+ <value>你没有权限读取这个时间线消息。</value>
+ </data>
+</root>
\ No newline at end of file diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs index 9d309e7e..7fe32cac 100644 --- a/Timeline/Services/TimelineService.cs +++ b/Timeline/Services/TimelineService.cs @@ -61,7 +61,7 @@ namespace Timeline.Services /// </exception>
/// <exception cref="UsernameBadFormatException">Thrown if <paramref name="author"/> is of bad format.</exception>
/// <exception cref="UserNotExistException">Thrown if <paramref name="author"/> does not exist.</exception>
- Task<long> Post(string name, string author, string content, DateTime? time);
+ Task<long> CreatePost(string name, string author, string content, DateTime? time);
/// <summary>
/// Set the visibility permission of a timeline.
@@ -104,11 +104,12 @@ namespace Timeline.Services Task SetDescription(string name, string description);
/// <summary>
- /// Add members to a timeline.
+ /// Remove members to a timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
- /// <param name="usernames">A list of new members' usernames</param>
- /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="usernames"/> is null.</exception>
+ /// <param name="add">A list of usernames of members to add. May be null.</param>
+ /// <param name="remove">A list of usernames of members to remove. May be null.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
/// <exception cref="TimelineNameBadFormatException">
/// Thrown when timeline name is of bad format.
/// For normal timeline, it means name is an empty string.
@@ -122,19 +123,26 @@ namespace Timeline.Services /// and the inner exception should be a <see cref="UserNotExistException"/>.
/// </exception>
/// <exception cref="TimelineMemberOperationUserException">
- /// Thrown when an exception occurs on users in the list.
+ /// Thrown when an exception occurs on the user list.
/// The inner exception is <see cref="UsernameBadFormatException"/>
- /// when one of the username is not valid.
+ /// when one of the username is invalid.
/// The inner exception is <see cref="UserNotExistException"/>
- /// when one of the user does not exist.
- /// </exception>
- Task AddMember(string name, IList<string> usernames);
+ /// when one of the user to add does not exist.
+ /// </exception>
+ /// <remarks>
+ /// Operating on a username that is of bad format always throws.
+ /// Add a user that already is a member has no effects.
+ /// Remove a user that is not a member also has not effects.
+ /// Add a user that does not exist will throw <see cref="TimelineMemberOperationUserException"/>.
+ /// But remove one does not throw.
+ /// </remarks>
+ Task ChangeMember(string name, IList<string>? add, IList<string>? remove);
/// <summary>
- /// Remove members to a timeline.
+ /// Verify whether a visitor has the permission to read a timeline.
/// </summary>
/// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
- /// <param name="usernames">A list of members' usernames</param>
+ /// <param name="username">The user to check on. Null means visitor without account.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> is null.</exception>
/// <exception cref="TimelineNameBadFormatException">
/// Thrown when timeline name is of bad format.
@@ -148,14 +156,34 @@ namespace Timeline.Services /// 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="TimelineMemberOperationUserException">
- /// Thrown when an exception occurs on the user list.
- /// The inner exception is <see cref="UsernameBadFormatException"/>
- /// when one of the username is invalid.
- /// The inner exception is <see cref="TimelineUserNotMemberException"/>
- /// when one of the user is not a member of the timeline.
+ /// <returns>True if can read, false if can't read.</returns>
+ Task<bool> HasReadPermission(string name, string? username);
+
+ /// <summary>
+ /// Verify whether a user is member of a timeline.
+ /// </summary>
+ /// <param name="name">Username or the timeline name. See remarks of <see cref="IBaseTimelineService"/>.</param>
+ /// <param name="username">The user to check on.</param>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="name"/> or <paramref name="username"/> is null.</exception>
+ /// <exception cref="TimelineNameBadFormatException">
+ /// Thrown when timeline name is of bad format.
+ /// For normal timeline, it means name is an empty string.
+ /// For personal timeline, it means the username is of bad format,
+ /// the inner exception should be a <see cref="UsernameBadFormatException"/>.
+ /// </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>
- Task RemoveMember(string name, IList<string> usernames);
+ /// <exception cref="UsernameBadFormatException">
+ /// Thrown when <paramref name="username"/> is not a valid username.
+ /// </exception>
+ /// <exception cref="UserNotExistException">
+ /// Thrown when user <paramref name="username"/> does not exist.</exception>
+ /// <returns>True if it is a member, false if not.</returns>
+ Task<bool> IsMemberOf(string name, string username);
}
/// <summary>
diff --git a/Timeline/Services/TimelineUserNotMemberException.cs b/Timeline/Services/TimelineUserNotMemberException.cs deleted file mode 100644 index 260beb02..00000000 --- a/Timeline/Services/TimelineUserNotMemberException.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System;
-
-namespace Timeline.Services
-{
- [Serializable]
- public class TimelineUserNotMemberException : Exception
- {
- public TimelineUserNotMemberException() : base(Resources.Services.Exception.TimelineUserNotMemberException) { }
- public TimelineUserNotMemberException(string message) : base(message) { }
- public TimelineUserNotMemberException(string message, Exception inner) : base(message, inner) { }
- protected TimelineUserNotMemberException(
- System.Runtime.Serialization.SerializationInfo info,
- System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
- }
-}
diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj index bd195475..7e1dd4ef 100644 --- a/Timeline/Timeline.csproj +++ b/Timeline/Timeline.csproj @@ -49,6 +49,11 @@ <AutoGen>True</AutoGen>
<DependentUpon>TestingI18nController.resx</DependentUpon>
</Compile>
+ <Compile Update="Resources\Controllers\TimelineController.Designer.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>TimelineController.resx</DependentUpon>
+ </Compile>
<Compile Update="Resources\Controllers\TokenController.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
@@ -119,6 +124,10 @@ <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>TestingI18nController.Designer.cs</LastGenOutput>
</EmbeddedResource>
+ <EmbeddedResource Update="Resources\Controllers\TimelineController.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>TimelineController.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
<EmbeddedResource Update="Resources\Controllers\TokenController.resx">
<SubType>Designer</SubType>
<Generator>ResXFileCodeGenerator</Generator>
|