aboutsummaryrefslogtreecommitdiff
path: root/BackEnd/Timeline
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2021-02-04 21:39:03 +0800
committerGitHub <noreply@github.com>2021-02-04 21:39:03 +0800
commit76d8616a6ba18f2bf86c46d865a9426e5accf55f (patch)
treeae0a8eb5f5a2d35114247f44a35e35e20aa2dee7 /BackEnd/Timeline
parent85247119bb82189baf100cd369e2f5993ee9d296 (diff)
parentc09ec62c64fe2d7cd09f7a2d9989ad96b43606a3 (diff)
downloadtimeline-76d8616a6ba18f2bf86c46d865a9426e5accf55f.tar.gz
timeline-76d8616a6ba18f2bf86c46d865a9426e5accf55f.tar.bz2
timeline-76d8616a6ba18f2bf86c46d865a9426e5accf55f.zip
Merge pull request #244 from crupest/color
Backend: Color
Diffstat (limited to 'BackEnd/Timeline')
-rw-r--r--BackEnd/Timeline/Controllers/TimelinePostController.cs6
-rw-r--r--BackEnd/Timeline/Entities/TimelineEntity.cs3
-rw-r--r--BackEnd/Timeline/Entities/TimelinePostEntity.cs3
-rw-r--r--BackEnd/Timeline/Migrations/20210204133241_AddColor.Designer.cs506
-rw-r--r--BackEnd/Timeline/Migrations/20210204133241_AddColor.cs33
-rw-r--r--BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs8
-rw-r--r--BackEnd/Timeline/Models/Http/HttpAutoMapperProfile.cs15
-rw-r--r--BackEnd/Timeline/Models/Http/HttpBookmarkTimelineMoveRequest.cs (renamed from BackEnd/Timeline/Models/Http/BookmarkTimeline.cs)0
-rw-r--r--BackEnd/Timeline/Models/Http/HttpChangePasswordRequest.cs23
-rw-r--r--BackEnd/Timeline/Models/Http/HttpCreateTokenRequest.cs25
-rw-r--r--BackEnd/Timeline/Models/Http/HttpCreateTokenResponse.cs19
-rw-r--r--BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs24
-rw-r--r--BackEnd/Timeline/Models/Http/HttpHighlightTimelineMoveRequest.cs (renamed from BackEnd/Timeline/Models/Http/HighlightTimeline.cs)0
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimeline.cs89
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimelineCreateRequest.cs18
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimelineLinks.cs26
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs37
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimelinePost.cs52
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimelinePostContent.cs35
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs26
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequestContent.cs24
-rw-r--r--BackEnd/Timeline/Models/Http/HttpUser.cs (renamed from BackEnd/Timeline/Models/Http/User.cs)30
-rw-r--r--BackEnd/Timeline/Models/Http/HttpUserLinks.cs31
-rw-r--r--BackEnd/Timeline/Models/Http/HttpUserPatchRequest.cs30
-rw-r--r--BackEnd/Timeline/Models/Http/HttpVerifyTokenRequest.cs15
-rw-r--r--BackEnd/Timeline/Models/Http/HttpVerifyTokenResponse.cs16
-rw-r--r--BackEnd/Timeline/Models/Http/Timeline.cs183
-rw-r--r--BackEnd/Timeline/Models/Http/TimelineController.cs90
-rw-r--r--BackEnd/Timeline/Models/Http/TokenController.cs62
-rw-r--r--BackEnd/Timeline/Models/Http/UserController.cs76
-rw-r--r--BackEnd/Timeline/Models/Mapper/TimelineMapper.cs2
-rw-r--r--BackEnd/Timeline/Models/Validation/ColorValidator.cs40
-rw-r--r--BackEnd/Timeline/Models/Validation/Validator.cs2
-rw-r--r--BackEnd/Timeline/Services/TimelinePostService.cs122
-rw-r--r--BackEnd/Timeline/Services/TimelineService.cs18
35 files changed, 1192 insertions, 497 deletions
diff --git a/BackEnd/Timeline/Controllers/TimelinePostController.cs b/BackEnd/Timeline/Controllers/TimelinePostController.cs
index afe9b36f..3f31decf 100644
--- a/BackEnd/Timeline/Controllers/TimelinePostController.cs
+++ b/BackEnd/Timeline/Controllers/TimelinePostController.cs
@@ -135,6 +135,8 @@ namespace Timeline.Controllers
TimelinePostEntity post;
+ TimelinePostCommonProperties properties = new TimelinePostCommonProperties { Color = body.Color, Time = body.Time };
+
if (content.Type == TimelinePostContentTypes.Text)
{
var text = content.Text;
@@ -142,7 +144,7 @@ namespace Timeline.Controllers
{
return BadRequest(ErrorResponse.Common.CustomMessage_InvalidModel(Resources.Messages.TimelineController_TextContentTextRequired));
}
- post = await _postService.CreateTextPost(timelineId, userId, text, body.Time);
+ post = await _postService.CreateTextPost(timelineId, userId, text, properties);
}
else if (content.Type == TimelinePostContentTypes.Image)
{
@@ -163,7 +165,7 @@ namespace Timeline.Controllers
try
{
- post = await _postService.CreateImagePost(timelineId, userId, data, body.Time);
+ post = await _postService.CreateImagePost(timelineId, userId, data, properties);
}
catch (ImageException)
{
diff --git a/BackEnd/Timeline/Entities/TimelineEntity.cs b/BackEnd/Timeline/Entities/TimelineEntity.cs
index 3e592673..23859cb3 100644
--- a/BackEnd/Timeline/Entities/TimelineEntity.cs
+++ b/BackEnd/Timeline/Entities/TimelineEntity.cs
@@ -41,6 +41,9 @@ namespace Timeline.Entities
[Column("visibility")]
public TimelineVisibility Visibility { get; set; }
+ [Column("color")]
+ public string? Color { get; set; }
+
[Column("create_time")]
public DateTime CreateTime { get; set; }
diff --git a/BackEnd/Timeline/Entities/TimelinePostEntity.cs b/BackEnd/Timeline/Entities/TimelinePostEntity.cs
index 07367fba..39b11a5b 100644
--- a/BackEnd/Timeline/Entities/TimelinePostEntity.cs
+++ b/BackEnd/Timeline/Entities/TimelinePostEntity.cs
@@ -34,6 +34,9 @@ namespace Timeline.Entities
[Column("extra_content")]
public string? ExtraContent { get; set; }
+ [Column("color")]
+ public string? Color { get; set; }
+
[Column("time")]
public DateTime Time { get; set; }
diff --git a/BackEnd/Timeline/Migrations/20210204133241_AddColor.Designer.cs b/BackEnd/Timeline/Migrations/20210204133241_AddColor.Designer.cs
new file mode 100644
index 00000000..8f8c2495
--- /dev/null
+++ b/BackEnd/Timeline/Migrations/20210204133241_AddColor.Designer.cs
@@ -0,0 +1,506 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Timeline.Entities;
+
+namespace Timeline.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20210204133241_AddColor")]
+ partial class AddColor
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.0");
+
+ modelBuilder.Entity("Timeline.Entities.BookmarkTimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long>("Rank")
+ .HasColumnType("INTEGER")
+ .HasColumnName("rank");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("bookmark_timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.DataEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("data");
+
+ b.Property<int>("Ref")
+ .HasColumnType("INTEGER")
+ .HasColumnName("ref");
+
+ b.Property<string>("Tag")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("tag");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tag")
+ .IsUnique();
+
+ b.ToTable("data");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("AddTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("add_time");
+
+ b.Property<long?>("OperatorId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("operator_id");
+
+ b.Property<long>("Order")
+ .HasColumnType("INTEGER")
+ .HasColumnName("order");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OperatorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("highlight_timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<byte[]>("Key")
+ .IsRequired()
+ .HasColumnType("BLOB")
+ .HasColumnName("key");
+
+ b.HasKey("Id");
+
+ b.ToTable("jwt_token");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("Color")
+ .HasColumnType("TEXT")
+ .HasColumnName("color");
+
+ b.Property<DateTime>("CreateTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time");
+
+ b.Property<long>("CurrentPostLocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("current_post_local_id");
+
+ b.Property<string>("Description")
+ .HasColumnType("TEXT")
+ .HasColumnName("description");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Name")
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.Property<DateTime>("NameLastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("name_last_modified");
+
+ b.Property<long>("OwnerId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("owner");
+
+ b.Property<string>("Title")
+ .HasColumnType("TEXT")
+ .HasColumnName("title");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<int>("Visibility")
+ .HasColumnType("INTEGER")
+ .HasColumnName("visibility");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("timeline_members");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<long?>("AuthorId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("author");
+
+ b.Property<string>("Color")
+ .HasColumnType("TEXT")
+ .HasColumnName("color");
+
+ b.Property<string>("Content")
+ .HasColumnType("TEXT")
+ .HasColumnName("content");
+
+ b.Property<string>("ContentType")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("content_type");
+
+ b.Property<string>("ExtraContent")
+ .HasColumnType("TEXT")
+ .HasColumnName("extra_content");
+
+ b.Property<DateTime>("LastUpdated")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_updated");
+
+ b.Property<long>("LocalId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("local_id");
+
+ b.Property<DateTime>("Time")
+ .HasColumnType("TEXT")
+ .HasColumnName("time");
+
+ b.Property<long>("TimelineId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("timeline");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("timeline_posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("DataTag")
+ .HasColumnType("TEXT")
+ .HasColumnName("data_tag");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified");
+
+ b.Property<string>("Type")
+ .HasColumnType("TEXT")
+ .HasColumnName("type");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("user_avatars");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<DateTime>("CreateTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("create_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<DateTime>("LastModified")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("last_modified")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<string>("Nickname")
+ .HasColumnType("TEXT")
+ .HasColumnName("nickname");
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("password");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("unique_id")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("username");
+
+ b.Property<DateTime>("UsernameChangeTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("username_change_time")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0L)
+ .HasColumnName("version");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasColumnName("id");
+
+ b.Property<string>("Permission")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasColumnName("permission");
+
+ b.Property<long>("UserId")
+ .HasColumnType("INTEGER")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("user_permission");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.BookmarkTimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany()
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Timeline");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Operator")
+ .WithMany()
+ .HasForeignKey("OperatorId");
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany()
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Operator");
+
+ b.Navigation("Timeline");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
+ .WithMany("Timelines")
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Members")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("TimelinesJoined")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Timeline");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
+ .WithMany("TimelinePosts")
+ .HasForeignKey("AuthorId");
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Posts")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Author");
+
+ b.Navigation("Timeline");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithOne("Avatar")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserPermissionEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("Permissions")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Navigation("Members");
+
+ b.Navigation("Posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Navigation("Avatar");
+
+ b.Navigation("Permissions");
+
+ b.Navigation("TimelinePosts");
+
+ b.Navigation("Timelines");
+
+ b.Navigation("TimelinesJoined");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Migrations/20210204133241_AddColor.cs b/BackEnd/Timeline/Migrations/20210204133241_AddColor.cs
new file mode 100644
index 00000000..96ad584c
--- /dev/null
+++ b/BackEnd/Timeline/Migrations/20210204133241_AddColor.cs
@@ -0,0 +1,33 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Timeline.Migrations
+{
+ public partial class AddColor : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn<string>(
+ name: "color",
+ table: "timelines",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.AddColumn<string>(
+ name: "color",
+ table: "timeline_posts",
+ type: "TEXT",
+ nullable: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "color",
+ table: "timelines");
+
+ migrationBuilder.DropColumn(
+ name: "color",
+ table: "timeline_posts");
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs
index 6b547a55..2e2b0d36 100644
--- a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs
+++ b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs
@@ -129,6 +129,10 @@ namespace Timeline.Migrations
.HasColumnType("INTEGER")
.HasColumnName("id");
+ b.Property<string>("Color")
+ .HasColumnType("TEXT")
+ .HasColumnName("color");
+
b.Property<DateTime>("CreateTime")
.HasColumnType("TEXT")
.HasColumnName("create_time");
@@ -214,6 +218,10 @@ namespace Timeline.Migrations
.HasColumnType("INTEGER")
.HasColumnName("author");
+ b.Property<string>("Color")
+ .HasColumnType("TEXT")
+ .HasColumnName("color");
+
b.Property<string>("Content")
.HasColumnType("TEXT")
.HasColumnName("content");
diff --git a/BackEnd/Timeline/Models/Http/HttpAutoMapperProfile.cs b/BackEnd/Timeline/Models/Http/HttpAutoMapperProfile.cs
new file mode 100644
index 00000000..426379b8
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpAutoMapperProfile.cs
@@ -0,0 +1,15 @@
+using AutoMapper;
+using Timeline.Services;
+
+namespace Timeline.Models.Http
+{
+
+ public class HttpAutoMapperProfile : Profile
+ {
+ public HttpAutoMapperProfile()
+ {
+ CreateMap<HttpUserPatchRequest, ModifyUserParams>();
+ CreateMap<HttpTimelinePatchRequest, TimelineChangePropertyParams>();
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/BookmarkTimeline.cs b/BackEnd/Timeline/Models/Http/HttpBookmarkTimelineMoveRequest.cs
index 14be1112..14be1112 100644
--- a/BackEnd/Timeline/Models/Http/BookmarkTimeline.cs
+++ b/BackEnd/Timeline/Models/Http/HttpBookmarkTimelineMoveRequest.cs
diff --git a/BackEnd/Timeline/Models/Http/HttpChangePasswordRequest.cs b/BackEnd/Timeline/Models/Http/HttpChangePasswordRequest.cs
new file mode 100644
index 00000000..0397d7ce
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpChangePasswordRequest.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel.DataAnnotations;
+using Timeline.Controllers;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Request model for <see cref="UserController.ChangePassword(HttpChangePasswordRequest)"/>.
+ /// </summary>
+ public class HttpChangePasswordRequest
+ {
+ /// <summary>
+ /// Old password.
+ /// </summary>
+ [Required(AllowEmptyStrings = false)]
+ public string OldPassword { get; set; } = default!;
+
+ /// <summary>
+ /// New password.
+ /// </summary>
+ [Required(AllowEmptyStrings = false)]
+ public string NewPassword { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpCreateTokenRequest.cs b/BackEnd/Timeline/Models/Http/HttpCreateTokenRequest.cs
new file mode 100644
index 00000000..2a20d490
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpCreateTokenRequest.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel.DataAnnotations;
+using Timeline.Controllers;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Request model for <see cref="TokenController.Create(HttpCreateTokenRequest)"/>.
+ /// </summary>
+ public class HttpCreateTokenRequest
+ {
+ /// <summary>
+ /// The username.
+ /// </summary>
+ public string Username { get; set; } = default!;
+ /// <summary>
+ /// The password.
+ /// </summary>
+ public string Password { get; set; } = default!;
+ /// <summary>
+ /// Optional token validation period. In days. If not specified, server will use a default one.
+ /// </summary>
+ [Range(1, 365)]
+ public int? Expire { get; set; }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpCreateTokenResponse.cs b/BackEnd/Timeline/Models/Http/HttpCreateTokenResponse.cs
new file mode 100644
index 00000000..78dd43c5
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpCreateTokenResponse.cs
@@ -0,0 +1,19 @@
+using Timeline.Controllers;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Response model for <see cref="TokenController.Create(HttpCreateTokenRequest)"/>.
+ /// </summary>
+ public class HttpCreateTokenResponse
+ {
+ /// <summary>
+ /// The token created.
+ /// </summary>
+ public string Token { get; set; } = default!;
+ /// <summary>
+ /// The user owning the token.
+ /// </summary>
+ public HttpUser User { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs b/BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs
new file mode 100644
index 00000000..7b221f73
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpCreateUserRequest.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel.DataAnnotations;
+using Timeline.Controllers;
+using Timeline.Models.Validation;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Request model for <see cref="UserController.CreateUser(HttpCreateUserRequest)"/>.
+ /// </summary>
+ public class HttpCreateUserRequest
+ {
+ /// <summary>
+ /// Username of the new user.
+ /// </summary>
+ [Required, Username]
+ public string Username { get; set; } = default!;
+
+ /// <summary>
+ /// Password of the new user.
+ /// </summary>
+ [Required, MinLength(1)]
+ public string Password { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HighlightTimeline.cs b/BackEnd/Timeline/Models/Http/HttpHighlightTimelineMoveRequest.cs
index 5af0e528..5af0e528 100644
--- a/BackEnd/Timeline/Models/Http/HighlightTimeline.cs
+++ b/BackEnd/Timeline/Models/Http/HttpHighlightTimelineMoveRequest.cs
diff --git a/BackEnd/Timeline/Models/Http/HttpTimeline.cs b/BackEnd/Timeline/Models/Http/HttpTimeline.cs
new file mode 100644
index 00000000..87ebf0bb
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpTimeline.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Info of a timeline.
+ /// </summary>
+ public class HttpTimeline
+ {
+ public HttpTimeline() { }
+
+ public HttpTimeline(string uniqueId, string title, string name, DateTime nameLastModifed, string description, HttpUser owner, TimelineVisibility visibility, List<HttpUser> members, string? color, DateTime createTime, DateTime lastModified, bool isHighlight, bool isBookmark, HttpTimelineLinks links)
+ {
+ UniqueId = uniqueId;
+ Title = title;
+ Name = name;
+ NameLastModifed = nameLastModifed;
+ Description = description;
+ Owner = owner;
+ Visibility = visibility;
+ Members = members;
+ Color = color;
+ CreateTime = createTime;
+ LastModified = lastModified;
+ IsHighlight = isHighlight;
+ IsBookmark = isBookmark;
+ _links = links;
+ }
+
+ /// <summary>
+ /// Unique id.
+ /// </summary>
+ public string UniqueId { get; set; } = default!;
+ /// <summary>
+ /// Title.
+ /// </summary>
+ public string Title { get; set; } = default!;
+ /// <summary>
+ /// Name of timeline.
+ /// </summary>
+ public string Name { get; set; } = default!;
+ /// <summary>
+ /// Last modified time of timeline name.
+ /// </summary>
+ public DateTime NameLastModifed { get; set; } = default!;
+ /// <summary>
+ /// Timeline description.
+ /// </summary>
+ public string Description { get; set; } = default!;
+ /// <summary>
+ /// Owner of the timeline.
+ /// </summary>
+ public HttpUser Owner { get; set; } = default!;
+ /// <summary>
+ /// Visibility of the timeline.
+ /// </summary>
+ public TimelineVisibility Visibility { get; set; }
+#pragma warning disable CA2227 // Collection properties should be read only
+ /// <summary>
+ /// Members of timeline.
+ /// </summary>
+ public List<HttpUser> Members { get; set; } = default!;
+#pragma warning restore CA2227 // Collection properties should be read only
+ /// <summary>
+ /// Color of timeline.
+ /// </summary>
+ public string? Color { get; set; }
+ /// <summary>
+ /// Create time of timeline.
+ /// </summary>
+ public DateTime CreateTime { get; set; } = default!;
+ /// <summary>
+ /// Last modified time of timeline.
+ /// </summary>
+ public DateTime LastModified { get; set; } = default!;
+
+ public bool IsHighlight { get; set; }
+
+ public bool IsBookmark { get; set; }
+
+#pragma warning disable CA1707 // Identifiers should not contain underscores
+ /// <summary>
+ /// Related links.
+ /// </summary>
+ public HttpTimelineLinks _links { get; set; } = default!;
+#pragma warning restore CA1707 // Identifiers should not contain underscores
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpTimelineCreateRequest.cs b/BackEnd/Timeline/Models/Http/HttpTimelineCreateRequest.cs
new file mode 100644
index 00000000..e9f57e46
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpTimelineCreateRequest.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+using Timeline.Models.Validation;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Create timeline request model.
+ /// </summary>
+ public class HttpTimelineCreateRequest
+ {
+ /// <summary>
+ /// Name of the new timeline. Must be a valid name.
+ /// </summary>
+ [Required]
+ [TimelineName]
+ public string Name { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpTimelineLinks.cs b/BackEnd/Timeline/Models/Http/HttpTimelineLinks.cs
new file mode 100644
index 00000000..722c1338
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpTimelineLinks.cs
@@ -0,0 +1,26 @@
+namespace Timeline.Models.Http
+{
+
+ /// <summary>
+ /// Related links for timeline.
+ /// </summary>
+ public class HttpTimelineLinks
+ {
+ public HttpTimelineLinks() { }
+
+ public HttpTimelineLinks(string self, string posts)
+ {
+ Self = self;
+ Posts = posts;
+ }
+
+ /// <summary>
+ /// Self.
+ /// </summary>
+ public string Self { get; set; } = default!;
+ /// <summary>
+ /// Posts url.
+ /// </summary>
+ public string Posts { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs
new file mode 100644
index 00000000..9accb6fc
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpTimelinePatchRequest.cs
@@ -0,0 +1,37 @@
+using Timeline.Models.Validation;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Patch timeline request model.
+ /// </summary>
+ public class HttpTimelinePatchRequest
+ {
+ /// <summary>
+ /// New name. Null for not change.
+ /// </summary>
+ [TimelineName]
+ public string? Name { get; set; }
+
+ /// <summary>
+ /// New title. Null for not change.
+ /// </summary>
+ public string? Title { get; set; }
+
+ /// <summary>
+ /// New description. Null for not change.
+ /// </summary>
+ public string? Description { get; set; }
+
+ /// <summary>
+ /// New visibility. Null for not change.
+ /// </summary>
+ public TimelineVisibility? Visibility { get; set; }
+
+ /// <summary>
+ /// New color. Null for not change.
+ /// </summary>
+ [Color]
+ public string? Color { get; set; }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs
new file mode 100644
index 00000000..5981d7a4
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs
@@ -0,0 +1,52 @@
+using System;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Info of a post.
+ /// </summary>
+ public class HttpTimelinePost
+ {
+ public HttpTimelinePost() { }
+
+ public HttpTimelinePost(long id, HttpTimelinePostContent? content, bool deleted, DateTime time, HttpUser? author, string? color, DateTime lastUpdated)
+ {
+ Id = id;
+ Content = content;
+ Deleted = deleted;
+ Time = time;
+ Author = author;
+ Color = color;
+ LastUpdated = lastUpdated;
+ }
+
+ /// <summary>
+ /// Post id.
+ /// </summary>
+ public long Id { get; set; }
+ /// <summary>
+ /// Content of the post. May be null if post is deleted.
+ /// </summary>
+ public HttpTimelinePostContent? Content { get; set; }
+ /// <summary>
+ /// True if post is deleted.
+ /// </summary>
+ public bool Deleted { get; set; }
+ /// <summary>
+ /// Post time.
+ /// </summary>
+ public DateTime Time { get; set; }
+ /// <summary>
+ /// The author. May be null if the user has been deleted.
+ /// </summary>
+ public HttpUser? Author { get; set; } = default!;
+ /// <summary>
+ /// The color.
+ /// </summary>
+ public string? Color { get; set; }
+ /// <summary>
+ /// Last updated time.
+ /// </summary>
+ public DateTime LastUpdated { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePostContent.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePostContent.cs
new file mode 100644
index 00000000..55ff1ac2
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpTimelinePostContent.cs
@@ -0,0 +1,35 @@
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Info of post content.
+ /// </summary>
+ public class HttpTimelinePostContent
+ {
+ public HttpTimelinePostContent() { }
+
+ public HttpTimelinePostContent(string type, string? text, string? url, string? eTag)
+ {
+ Type = type;
+ Text = text;
+ Url = url;
+ ETag = eTag;
+ }
+
+ /// <summary>
+ /// Type of the post content.
+ /// </summary>
+ public string Type { get; set; } = default!;
+ /// <summary>
+ /// If post is of text type. This is the text.
+ /// </summary>
+ public string? Text { get; set; }
+ /// <summary>
+ /// If post is of image type. This is the image url.
+ /// </summary>
+ public string? Url { get; set; }
+ /// <summary>
+ /// If post has data (currently it means it's a image post), this is the data etag.
+ /// </summary>
+ public string? ETag { get; set; }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs
new file mode 100644
index 00000000..b25adf36
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequest.cs
@@ -0,0 +1,26 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using Timeline.Models.Validation;
+
+namespace Timeline.Models.Http
+{
+ public class HttpTimelinePostCreateRequest
+ {
+ /// <summary>
+ /// Content of the new post.
+ /// </summary>
+ [Required]
+ public HttpTimelinePostCreateRequestContent Content { get; set; } = default!;
+
+ /// <summary>
+ /// Time of the post. If not set, current time will be used.
+ /// </summary>
+ public DateTime? Time { get; set; }
+
+ /// <summary>
+ /// Color of the post.
+ /// </summary>
+ [Color]
+ public string? Color { get; set; }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequestContent.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequestContent.cs
new file mode 100644
index 00000000..f4b300a9
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpTimelinePostCreateRequestContent.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Content of post create request.
+ /// </summary>
+ public class HttpTimelinePostCreateRequestContent
+ {
+ /// <summary>
+ /// Type of post content.
+ /// </summary>
+ [Required]
+ public string Type { get; set; } = default!;
+ /// <summary>
+ /// If post is of text type, this is the text.
+ /// </summary>
+ public string? Text { get; set; }
+ /// <summary>
+ /// If post is of image type, this is base64 of image data.
+ /// </summary>
+ public string? Data { get; set; }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/User.cs b/BackEnd/Timeline/Models/Http/HttpUser.cs
index 994c08bf..4b82264c 100644
--- a/BackEnd/Timeline/Models/Http/User.cs
+++ b/BackEnd/Timeline/Models/Http/HttpUser.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace Timeline.Models.Http
{
@@ -43,32 +43,4 @@ namespace Timeline.Models.Http
public HttpUserLinks _links { get; set; } = default!;
#pragma warning restore CA1707 // Identifiers should not contain underscores
}
-
- /// <summary>
- /// Related links for user.
- /// </summary>
- public class HttpUserLinks
- {
- public HttpUserLinks() { }
-
- public HttpUserLinks(string self, string avatar, string timeline)
- {
- Self = self;
- Avatar = avatar;
- Timeline = timeline;
- }
-
- /// <summary>
- /// Self.
- /// </summary>
- public string Self { get; set; } = default!;
- /// <summary>
- /// Avatar url.
- /// </summary>
- public string Avatar { get; set; } = default!;
- /// <summary>
- /// Personal timeline url.
- /// </summary>
- public string Timeline { get; set; } = default!;
- }
}
diff --git a/BackEnd/Timeline/Models/Http/HttpUserLinks.cs b/BackEnd/Timeline/Models/Http/HttpUserLinks.cs
new file mode 100644
index 00000000..d5f909c2
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpUserLinks.cs
@@ -0,0 +1,31 @@
+namespace Timeline.Models.Http
+{
+
+ /// <summary>
+ /// Related links for user.
+ /// </summary>
+ public class HttpUserLinks
+ {
+ public HttpUserLinks() { }
+
+ public HttpUserLinks(string self, string avatar, string timeline)
+ {
+ Self = self;
+ Avatar = avatar;
+ Timeline = timeline;
+ }
+
+ /// <summary>
+ /// Self.
+ /// </summary>
+ public string Self { get; set; } = default!;
+ /// <summary>
+ /// Avatar url.
+ /// </summary>
+ public string Avatar { get; set; } = default!;
+ /// <summary>
+ /// Personal timeline url.
+ /// </summary>
+ public string Timeline { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpUserPatchRequest.cs b/BackEnd/Timeline/Models/Http/HttpUserPatchRequest.cs
new file mode 100644
index 00000000..e7a3d8e3
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpUserPatchRequest.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel.DataAnnotations;
+using Timeline.Controllers;
+using Timeline.Models.Validation;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Request model for <see cref="UserController.Patch(HttpUserPatchRequest, string)"/>.
+ /// </summary>
+ public class HttpUserPatchRequest
+ {
+ /// <summary>
+ /// New username. Null if not change. Need to be administrator.
+ /// </summary>
+ [Username]
+ public string? Username { get; set; }
+
+ /// <summary>
+ /// New password. Null if not change. Need to be administrator.
+ /// </summary>
+ [MinLength(1)]
+ public string? Password { get; set; }
+
+ /// <summary>
+ /// New nickname. Null if not change. Need to be administrator to change other's.
+ /// </summary>
+ [Nickname]
+ public string? Nickname { get; set; }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpVerifyTokenRequest.cs b/BackEnd/Timeline/Models/Http/HttpVerifyTokenRequest.cs
new file mode 100644
index 00000000..98f86455
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpVerifyTokenRequest.cs
@@ -0,0 +1,15 @@
+using Timeline.Controllers;
+
+namespace Timeline.Models.Http
+{
+ /// <summary>
+ /// Request model for <see cref="TokenController.Verify(HttpVerifyTokenRequest)"/>.
+ /// </summary>
+ public class HttpVerifyTokenRequest
+ {
+ /// <summary>
+ /// The token to verify.
+ /// </summary>
+ public string Token { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/HttpVerifyTokenResponse.cs b/BackEnd/Timeline/Models/Http/HttpVerifyTokenResponse.cs
new file mode 100644
index 00000000..ae8eb018
--- /dev/null
+++ b/BackEnd/Timeline/Models/Http/HttpVerifyTokenResponse.cs
@@ -0,0 +1,16 @@
+using Timeline.Controllers;
+
+namespace Timeline.Models.Http
+{
+
+ /// <summary>
+ /// Response model for <see cref="TokenController.Verify(HttpVerifyTokenRequest)"/>.
+ /// </summary>
+ public class HttpVerifyTokenResponse
+ {
+ /// <summary>
+ /// The user owning the token.
+ /// </summary>
+ public HttpUser User { get; set; } = default!;
+ }
+}
diff --git a/BackEnd/Timeline/Models/Http/Timeline.cs b/BackEnd/Timeline/Models/Http/Timeline.cs
deleted file mode 100644
index 5e5889f6..00000000
--- a/BackEnd/Timeline/Models/Http/Timeline.cs
+++ /dev/null
@@ -1,183 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Timeline.Models.Http
-{
- /// <summary>
- /// Info of post content.
- /// </summary>
- public class HttpTimelinePostContent
- {
- public HttpTimelinePostContent() { }
-
- public HttpTimelinePostContent(string type, string? text, string? url, string? eTag)
- {
- Type = type;
- Text = text;
- Url = url;
- ETag = eTag;
- }
-
- /// <summary>
- /// Type of the post content.
- /// </summary>
- public string Type { get; set; } = default!;
- /// <summary>
- /// If post is of text type. This is the text.
- /// </summary>
- public string? Text { get; set; }
- /// <summary>
- /// If post is of image type. This is the image url.
- /// </summary>
- public string? Url { get; set; }
- /// <summary>
- /// If post has data (currently it means it's a image post), this is the data etag.
- /// </summary>
- public string? ETag { get; set; }
- }
-
- /// <summary>
- /// Info of a post.
- /// </summary>
- public class HttpTimelinePost
- {
- public HttpTimelinePost() { }
-
- public HttpTimelinePost(long id, HttpTimelinePostContent? content, bool deleted, DateTime time, HttpUser? author, DateTime lastUpdated)
- {
- Id = id;
- Content = content;
- Deleted = deleted;
- Time = time;
- Author = author;
- LastUpdated = lastUpdated;
- }
-
- /// <summary>
- /// Post id.
- /// </summary>
- public long Id { get; set; }
- /// <summary>
- /// Content of the post. May be null if post is deleted.
- /// </summary>
- public HttpTimelinePostContent? Content { get; set; }
- /// <summary>
- /// True if post is deleted.
- /// </summary>
- public bool Deleted { get; set; }
- /// <summary>
- /// Post time.
- /// </summary>
- public DateTime Time { get; set; }
- /// <summary>
- /// The author. May be null if the user has been deleted.
- /// </summary>
- public HttpUser? Author { get; set; } = default!;
- /// <summary>
- /// Last updated time.
- /// </summary>
- public DateTime LastUpdated { get; set; } = default!;
- }
-
- /// <summary>
- /// Info of a timeline.
- /// </summary>
- public class HttpTimeline
- {
- public HttpTimeline() { }
-
- public HttpTimeline(string uniqueId, string title, string name, DateTime nameLastModifed, string description, HttpUser owner, TimelineVisibility visibility, List<HttpUser> members, DateTime createTime, DateTime lastModified, bool isHighlight, bool isBookmark, HttpTimelineLinks links)
- {
- UniqueId = uniqueId;
- Title = title;
- Name = name;
- NameLastModifed = nameLastModifed;
- Description = description;
- Owner = owner;
- Visibility = visibility;
- Members = members;
- CreateTime = createTime;
- LastModified = lastModified;
- IsHighlight = isHighlight;
- IsBookmark = isBookmark;
- _links = links;
- }
-
- /// <summary>
- /// Unique id.
- /// </summary>
- public string UniqueId { get; set; } = default!;
- /// <summary>
- /// Title.
- /// </summary>
- public string Title { get; set; } = default!;
- /// <summary>
- /// Name of timeline.
- /// </summary>
- public string Name { get; set; } = default!;
- /// <summary>
- /// Last modified time of timeline name.
- /// </summary>
- public DateTime NameLastModifed { get; set; } = default!;
- /// <summary>
- /// Timeline description.
- /// </summary>
- public string Description { get; set; } = default!;
- /// <summary>
- /// Owner of the timeline.
- /// </summary>
- public HttpUser Owner { get; set; } = default!;
- /// <summary>
- /// Visibility of the timeline.
- /// </summary>
- public TimelineVisibility Visibility { get; set; }
-#pragma warning disable CA2227 // Collection properties should be read only
- /// <summary>
- /// Members of timeline.
- /// </summary>
- public List<HttpUser> Members { get; set; } = default!;
-#pragma warning restore CA2227 // Collection properties should be read only
- /// <summary>
- /// Create time of timeline.
- /// </summary>
- public DateTime CreateTime { get; set; } = default!;
- /// <summary>
- /// Last modified time of timeline.
- /// </summary>
- public DateTime LastModified { get; set; } = default!;
-
- public bool IsHighlight { get; set; }
-
- public bool IsBookmark { get; set; }
-
-#pragma warning disable CA1707 // Identifiers should not contain underscores
- /// <summary>
- /// Related links.
- /// </summary>
- public HttpTimelineLinks _links { get; set; } = default!;
-#pragma warning restore CA1707 // Identifiers should not contain underscores
- }
-
- /// <summary>
- /// Related links for timeline.
- /// </summary>
- public class HttpTimelineLinks
- {
- public HttpTimelineLinks() { }
-
- public HttpTimelineLinks(string self, string posts)
- {
- Self = self;
- Posts = posts;
- }
-
- /// <summary>
- /// Self.
- /// </summary>
- public string Self { get; set; } = default!;
- /// <summary>
- /// Posts url.
- /// </summary>
- public string Posts { get; set; } = default!;
- }
-}
diff --git a/BackEnd/Timeline/Models/Http/TimelineController.cs b/BackEnd/Timeline/Models/Http/TimelineController.cs
deleted file mode 100644
index 79be1826..00000000
--- a/BackEnd/Timeline/Models/Http/TimelineController.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using AutoMapper;
-using System;
-using System.ComponentModel.DataAnnotations;
-using Timeline.Models.Validation;
-using Timeline.Services;
-
-namespace Timeline.Models.Http
-{
- /// <summary>
- /// Content of post create request.
- /// </summary>
- public class HttpTimelinePostCreateRequestContent
- {
- /// <summary>
- /// Type of post content.
- /// </summary>
- [Required]
- public string Type { get; set; } = default!;
- /// <summary>
- /// If post is of text type, this is the text.
- /// </summary>
- public string? Text { get; set; }
- /// <summary>
- /// If post is of image type, this is base64 of image data.
- /// </summary>
- public string? Data { get; set; }
- }
-
- public class HttpTimelinePostCreateRequest
- {
- /// <summary>
- /// Content of the new post.
- /// </summary>
- [Required]
- public HttpTimelinePostCreateRequestContent Content { get; set; } = default!;
-
- /// <summary>
- /// Time of the post. If not set, current time will be used.
- /// </summary>
- public DateTime? Time { get; set; }
- }
-
- /// <summary>
- /// Create timeline request model.
- /// </summary>
- public class HttpTimelineCreateRequest
- {
- /// <summary>
- /// Name of the new timeline. Must be a valid name.
- /// </summary>
- [Required]
- [TimelineName]
- public string Name { get; set; } = default!;
- }
-
- /// <summary>
- /// Patch timeline request model.
- /// </summary>
- public class HttpTimelinePatchRequest
- {
- /// <summary>
- /// New name. Null for not change.
- /// </summary>
- [TimelineName]
- public string? Name { get; set; }
-
- /// <summary>
- /// New title. Null for not change.
- /// </summary>
- public string? Title { get; set; }
-
- /// <summary>
- /// New description. Null for not change.
- /// </summary>
- public string? Description { get; set; }
-
- /// <summary>
- /// New visibility. Null for not change.
- /// </summary>
- public TimelineVisibility? Visibility { get; set; }
- }
-
- public class HttpTimelineControllerAutoMapperProfile : Profile
- {
- public HttpTimelineControllerAutoMapperProfile()
- {
- CreateMap<HttpTimelinePatchRequest, TimelineChangePropertyParams>();
- }
- }
-}
diff --git a/BackEnd/Timeline/Models/Http/TokenController.cs b/BackEnd/Timeline/Models/Http/TokenController.cs
deleted file mode 100644
index a5cbba14..00000000
--- a/BackEnd/Timeline/Models/Http/TokenController.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using Timeline.Controllers;
-
-namespace Timeline.Models.Http
-{
- /// <summary>
- /// Request model for <see cref="TokenController.Create(HttpCreateTokenRequest)"/>.
- /// </summary>
- public class HttpCreateTokenRequest
- {
- /// <summary>
- /// The username.
- /// </summary>
- public string Username { get; set; } = default!;
- /// <summary>
- /// The password.
- /// </summary>
- public string Password { get; set; } = default!;
- /// <summary>
- /// Optional token validation period. In days. If not specified, server will use a default one.
- /// </summary>
- [Range(1, 365)]
- public int? Expire { get; set; }
- }
-
- /// <summary>
- /// Response model for <see cref="TokenController.Create(HttpCreateTokenRequest)"/>.
- /// </summary>
- public class HttpCreateTokenResponse
- {
- /// <summary>
- /// The token created.
- /// </summary>
- public string Token { get; set; } = default!;
- /// <summary>
- /// The user owning the token.
- /// </summary>
- public HttpUser User { get; set; } = default!;
- }
-
- /// <summary>
- /// Request model for <see cref="TokenController.Verify(HttpVerifyTokenRequest)"/>.
- /// </summary>
- public class HttpVerifyTokenRequest
- {
- /// <summary>
- /// The token to verify.
- /// </summary>
- public string Token { get; set; } = default!;
- }
-
- /// <summary>
- /// Response model for <see cref="TokenController.Verify(HttpVerifyTokenRequest)"/>.
- /// </summary>
- public class HttpVerifyTokenResponse
- {
- /// <summary>
- /// The user owning the token.
- /// </summary>
- public HttpUser User { get; set; } = default!;
- }
-}
diff --git a/BackEnd/Timeline/Models/Http/UserController.cs b/BackEnd/Timeline/Models/Http/UserController.cs
deleted file mode 100644
index 1b4d09ec..00000000
--- a/BackEnd/Timeline/Models/Http/UserController.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using AutoMapper;
-using System.ComponentModel.DataAnnotations;
-using Timeline.Controllers;
-using Timeline.Models.Validation;
-using Timeline.Services;
-
-namespace Timeline.Models.Http
-{
- /// <summary>
- /// Request model for <see cref="UserController.Patch(HttpUserPatchRequest, string)"/>.
- /// </summary>
- public class HttpUserPatchRequest
- {
- /// <summary>
- /// New username. Null if not change. Need to be administrator.
- /// </summary>
- [Username]
- public string? Username { get; set; }
-
- /// <summary>
- /// New password. Null if not change. Need to be administrator.
- /// </summary>
- [MinLength(1)]
- public string? Password { get; set; }
-
- /// <summary>
- /// New nickname. Null if not change. Need to be administrator to change other's.
- /// </summary>
- [Nickname]
- public string? Nickname { get; set; }
- }
-
- /// <summary>
- /// Request model for <see cref="UserController.CreateUser(HttpCreateUserRequest)"/>.
- /// </summary>
- public class HttpCreateUserRequest
- {
- /// <summary>
- /// Username of the new user.
- /// </summary>
- [Required, Username]
- public string Username { get; set; } = default!;
-
- /// <summary>
- /// Password of the new user.
- /// </summary>
- [Required, MinLength(1)]
- public string Password { get; set; } = default!;
- }
-
- /// <summary>
- /// Request model for <see cref="UserController.ChangePassword(HttpChangePasswordRequest)"/>.
- /// </summary>
- public class HttpChangePasswordRequest
- {
- /// <summary>
- /// Old password.
- /// </summary>
- [Required(AllowEmptyStrings = false)]
- public string OldPassword { get; set; } = default!;
-
- /// <summary>
- /// New password.
- /// </summary>
- [Required(AllowEmptyStrings = false)]
- public string NewPassword { get; set; } = default!;
- }
-
- public class HttpUserControllerModelAutoMapperProfile : Profile
- {
- public HttpUserControllerModelAutoMapperProfile()
- {
- CreateMap<HttpUserPatchRequest, ModifyUserParams>();
- }
- }
-}
diff --git a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs
index 79a6fa1d..94e55237 100644
--- a/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs
+++ b/BackEnd/Timeline/Models/Mapper/TimelineMapper.cs
@@ -42,6 +42,7 @@ namespace Timeline.Models.Mapper
owner: await _userMapper.MapToHttp(entity.Owner, urlHelper),
visibility: entity.Visibility,
members: await _userMapper.MapToHttp(entity.Members.Select(m => m.User).ToList(), urlHelper),
+ color: entity.Color,
createTime: entity.CreateTime,
lastModified: entity.LastModified,
isHighlight: await _highlightTimelineService.IsHighlightTimeline(entity.Id),
@@ -105,6 +106,7 @@ namespace Timeline.Models.Mapper
deleted: content is null,
time: entity.Time,
author: author,
+ color: entity.Color,
lastUpdated: entity.LastUpdated
);
}
diff --git a/BackEnd/Timeline/Models/Validation/ColorValidator.cs b/BackEnd/Timeline/Models/Validation/ColorValidator.cs
new file mode 100644
index 00000000..c5ad833d
--- /dev/null
+++ b/BackEnd/Timeline/Models/Validation/ColorValidator.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace Timeline.Models.Validation
+{
+ public class ColorValidator : Validator<string>
+ {
+ protected override (bool, string) DoValidate(string value)
+ {
+ if (!value.StartsWith('#'))
+ {
+ return (false, "Color must starts with '#'.");
+ }
+
+ if (value.Length != 7)
+ {
+ return (false, "A color string must have 7 chars.");
+ }
+
+ for (int i = 1; i < 7; i++)
+ {
+ var c = value[i];
+ if (!((c >= '0' && c <= '9') || (c >= 'a' || c <= 'f') || (c >= 'A' | c <= 'F')))
+ {
+ return (false, $"Char at index {i} is not a hex character.");
+ }
+ }
+
+ return (true, GetSuccessMessage());
+ }
+ }
+
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
+ public class ColorAttribute : ValidateWithAttribute
+ {
+ public ColorAttribute() : base(typeof(ColorValidator))
+ {
+
+ }
+ }
+}
diff --git a/BackEnd/Timeline/Models/Validation/Validator.cs b/BackEnd/Timeline/Models/Validation/Validator.cs
index b7e754d3..ec6cc0af 100644
--- a/BackEnd/Timeline/Models/Validation/Validator.cs
+++ b/BackEnd/Timeline/Models/Validation/Validator.cs
@@ -51,7 +51,7 @@ namespace Timeline.Models.Validation
public (bool, string) Validate(object? value)
{
- if (value == null)
+ if (value is null)
{
if (PermitNull)
return (true, GetSuccessMessage());
diff --git a/BackEnd/Timeline/Services/TimelinePostService.cs b/BackEnd/Timeline/Services/TimelinePostService.cs
index a8bdbf92..c2b773ff 100644
--- a/BackEnd/Timeline/Services/TimelinePostService.cs
+++ b/BackEnd/Timeline/Services/TimelinePostService.cs
@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Helpers;
using Timeline.Models;
+using Timeline.Models.Validation;
using Timeline.Services.Exceptions;
using static Timeline.Resources.Services.TimelineService;
@@ -23,6 +24,14 @@ namespace Timeline.Services
public DateTime? LastModified { get; set; } // TODO: Why nullable?
}
+ public class TimelinePostCommonProperties
+ {
+ public string? Color { get; set; }
+
+ /// <summary>If not set, current time is used.</summary>
+ public DateTime? Time { get; set; }
+ }
+
public interface ITimelinePostService
{
/// <summary>
@@ -64,12 +73,12 @@ namespace Timeline.Services
/// <param name="timelineId">The id of the timeline to create post against.</param>
/// <param name="authorId">The author's user id.</param>
/// <param name="text">The content text.</param>
- /// <param name="time">The time of the post. If null, then current time is used.</param>
+ /// <param name="properties">Some properties.</param>
/// <returns>The info of the created post.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="text"/> is null.</exception>
/// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <exception cref="UserNotExistException">Thrown if user of <paramref name="authorId"/> does not exist.</exception>
- Task<TimelinePostEntity> CreateTextPost(long timelineId, long authorId, string text, DateTime? time);
+ Task<TimelinePostEntity> CreateTextPost(long timelineId, long authorId, string text, TimelinePostCommonProperties? properties = null);
/// <summary>
/// Create a new image post in timeline.
@@ -77,13 +86,13 @@ namespace Timeline.Services
/// <param name="timelineId">The id of the timeline to create post against.</param>
/// <param name="authorId">The author's user id.</param>
/// <param name="imageData">The image data.</param>
- /// <param name="time">The time of the post. If null, then use current time.</param>
+ /// <param name="properties">Some properties.</param>
/// <returns>The info of the created post.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="imageData"/> is null.</exception>
/// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
/// <exception cref="UserNotExistException">Thrown if user of <paramref name="authorId"/> does not exist.</exception>
/// <exception cref="ImageException">Thrown if data is not a image. Validated by <see cref="ImageValidator"/>.</exception>
- Task<TimelinePostEntity> CreateImagePost(long timelineId, long authorId, byte[] imageData, DateTime? time);
+ Task<TimelinePostEntity> CreateImagePost(long timelineId, long authorId, byte[] imageData, TimelinePostCommonProperties? properties = null);
/// <summary>
/// Delete a post.
@@ -128,17 +137,18 @@ namespace Timeline.Services
private readonly ILogger<TimelinePostService> _logger;
private readonly DatabaseContext _database;
private readonly IBasicTimelineService _basicTimelineService;
- private readonly IUserService _userService;
+ private readonly IBasicUserService _basicUserService;
private readonly IDataManager _dataManager;
private readonly IImageValidator _imageValidator;
private readonly IClock _clock;
+ private readonly ColorValidator _colorValidator = new ColorValidator();
- public TimelinePostService(ILogger<TimelinePostService> logger, DatabaseContext database, IBasicTimelineService basicTimelineService, IUserService userService, IDataManager dataManager, IImageValidator imageValidator, IClock clock)
+ public TimelinePostService(ILogger<TimelinePostService> logger, DatabaseContext database, IBasicTimelineService basicTimelineService, IBasicUserService basicUserService, IDataManager dataManager, IImageValidator imageValidator, IClock clock)
{
_logger = logger;
_database = database;
_basicTimelineService = basicTimelineService;
- _userService = userService;
+ _basicUserService = basicUserService;
_dataManager = dataManager;
_imageValidator = imageValidator;
_clock = clock;
@@ -150,6 +160,12 @@ namespace Timeline.Services
throw new TimelineNotExistException(timelineId);
}
+ private async Task CheckUserExistence(long userId)
+ {
+ if (!await _basicUserService.CheckUserExistence(userId))
+ throw new UserNotExistException(userId);
+ }
+
public async Task<List<TimelinePostEntity>> GetPosts(long timelineId, DateTime? modifiedSince = null, bool includeDeleted = false)
{
await CheckTimelineExistence(timelineId);
@@ -238,79 +254,77 @@ namespace Timeline.Services
};
}
- public async Task<TimelinePostEntity> CreateTextPost(long timelineId, long authorId, string text, DateTime? time)
+ private async Task<TimelinePostEntity> GeneralCreatePost(long timelineId, long authorId, TimelinePostCommonProperties? properties, Func<TimelinePostEntity, Task> saveContent)
{
- if (text is null)
- throw new ArgumentNullException(nameof(text));
+ if (properties is not null)
+ {
+ if (!_colorValidator.Validate(properties.Color, out var message))
+ {
+ throw new ArgumentException(message, nameof(properties));
+ }
+ properties.Time = properties.Time?.MyToUtc();
+ }
await CheckTimelineExistence(timelineId);
-
- time = time?.MyToUtc();
-
- var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
-
- var author = await _userService.GetUser(authorId);
+ await CheckUserExistence(authorId);
var currentTime = _clock.GetCurrentTime();
- var finalTime = time ?? currentTime;
-
- timelineEntity.CurrentPostLocalId += 1;
+ var finalTime = properties?.Time ?? currentTime;
var postEntity = new TimelinePostEntity
{
- LocalId = timelineEntity.CurrentPostLocalId,
- ContentType = TimelinePostContentTypes.Text,
- Content = text,
AuthorId = authorId,
TimelineId = timelineId,
Time = finalTime,
- LastUpdated = currentTime
+ LastUpdated = currentTime,
+ Color = properties?.Color
};
+
+ await saveContent(postEntity);
+
+ var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
+ timelineEntity.CurrentPostLocalId += 1;
+ postEntity.LocalId = timelineEntity.CurrentPostLocalId;
+
_database.TimelinePosts.Add(postEntity);
+
await _database.SaveChangesAsync();
return postEntity;
}
- public async Task<TimelinePostEntity> CreateImagePost(long timelineId, long authorId, byte[] data, DateTime? time)
+ public async Task<TimelinePostEntity> CreateTextPost(long timelineId, long authorId, string text, TimelinePostCommonProperties? properties = null)
{
- if (data is null)
- throw new ArgumentNullException(nameof(data));
-
- await CheckTimelineExistence(timelineId);
-
- time = time?.MyToUtc();
-
- var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).SingleAsync();
-
- var author = await _userService.GetUser(authorId);
-
- var imageFormat = await _imageValidator.Validate(data);
+ if (text is null)
+ throw new ArgumentNullException(nameof(text));
- var imageFormatText = imageFormat.DefaultMimeType;
+ return await GeneralCreatePost(timelineId, authorId, properties, (entity) =>
+ {
+ entity.ContentType = TimelinePostContentTypes.Text;
+ entity.Content = text;
- var tag = await _dataManager.RetainEntry(data);
+ return Task.CompletedTask;
+ });
+ }
- var currentTime = _clock.GetCurrentTime();
- var finalTime = time ?? currentTime;
+ public async Task<TimelinePostEntity> CreateImagePost(long timelineId, long authorId, byte[] data, TimelinePostCommonProperties? properties = null)
+ {
+ if (data is null)
+ throw new ArgumentNullException(nameof(data));
- timelineEntity.CurrentPostLocalId += 1;
+ await CheckTimelineExistence(timelineId);
- var postEntity = new TimelinePostEntity
+ return await GeneralCreatePost(timelineId, authorId, properties, async (entity) =>
{
- 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();
+ var imageFormat = await _imageValidator.Validate(data);
+ var imageFormatText = imageFormat.DefaultMimeType;
- return postEntity;
+ var tag = await _dataManager.RetainEntry(data);
+
+ entity.ContentType = TimelinePostContentTypes.Image;
+ entity.Content = tag;
+ entity.ExtraContent = imageFormatText;
+ });
}
public async Task DeletePost(long timelineId, long postId)
diff --git a/BackEnd/Timeline/Services/TimelineService.cs b/BackEnd/Timeline/Services/TimelineService.cs
index f4141752..bed1c99b 100644
--- a/BackEnd/Timeline/Services/TimelineService.cs
+++ b/BackEnd/Timeline/Services/TimelineService.cs
@@ -53,6 +53,7 @@ namespace Timeline.Services
public string? Title { get; set; }
public string? Description { get; set; }
public TimelineVisibility? Visibility { get; set; }
+ public string? Color { get; set; }
}
/// <summary>
@@ -186,6 +187,8 @@ namespace Timeline.Services
private readonly TimelineNameValidator _timelineNameValidator = new TimelineNameValidator();
+ private readonly ColorValidator _colorValidator = new ColorValidator();
+
private void ValidateTimelineName(string name, string paramName)
{
if (!_timelineNameValidator.Validate(name, out var message))
@@ -212,6 +215,15 @@ namespace Timeline.Services
if (newProperties.Name is not null)
ValidateTimelineName(newProperties.Name, nameof(newProperties));
+ if (newProperties.Color is not null)
+ {
+ var (result, message) = _colorValidator.Validate(newProperties.Color);
+ if (!result)
+ {
+ throw new ArgumentException(message, nameof(newProperties));
+ }
+ }
+
var entity = await _database.Timelines.Where(t => t.Id == id).SingleOrDefaultAsync();
if (entity is null)
@@ -251,6 +263,12 @@ namespace Timeline.Services
entity.Visibility = newProperties.Visibility.Value;
}
+ if (newProperties.Color is not null)
+ {
+ changed = true;
+ entity.Color = newProperties.Color;
+ }
+
if (changed)
{
var currentTime = _clock.GetCurrentTime();