From 080330966333fe61b6a9d5413c6b05b9ea77f4dc Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 26 Nov 2020 20:02:03 +0800 Subject: feat: Add highlight timeline entity and service. --- BackEnd/Timeline/Entities/DatabaseContext.cs | 2 ++ .../Timeline/Entities/HighlightTimelineEntity.cs | 24 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 BackEnd/Timeline/Entities/HighlightTimelineEntity.cs (limited to 'BackEnd/Timeline/Entities') diff --git a/BackEnd/Timeline/Entities/DatabaseContext.cs b/BackEnd/Timeline/Entities/DatabaseContext.cs index e4203392..4205c2cf 100644 --- a/BackEnd/Timeline/Entities/DatabaseContext.cs +++ b/BackEnd/Timeline/Entities/DatabaseContext.cs @@ -29,6 +29,8 @@ namespace Timeline.Entities public DbSet Timelines { get; set; } = default!; public DbSet TimelinePosts { get; set; } = default!; public DbSet TimelineMembers { get; set; } = default!; + public DbSet HighlightTimelines { get; set; } = default!; + public DbSet JwtToken { get; set; } = default!; public DbSet Data { get; set; } = default!; } diff --git a/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs b/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs new file mode 100644 index 00000000..0a38c8a6 --- /dev/null +++ b/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Timeline.Entities +{ + [Table("highlight_timelines")] + public record HighlightTimelineEntity + { + [Key, Column("id"), DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long Id { get; set; } + + [Column("timeline_id")] + public long TimelineId { get; set; } + + [ForeignKey(nameof(TimelineId))] + public TimelineEntity Timeline { get; set; } = default!; + + [Column("operator_id")] + public long? OperatorId { get; set; } + + [ForeignKey(nameof(OperatorId))] + public UserEntity? Operator { get; set; } + } +} -- cgit v1.2.3 From 3971aeb4b0d1a7566b6c9d3c88984c488fdf8074 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 27 Nov 2020 01:26:23 +0800 Subject: ... --- BackEnd/Timeline.Tests/Helpers/TestDatabase.cs | 2 +- .../Services/HighlightTimelineServiceTest.cs | 35 ++++++++++++++++++---- .../Services/TimelinePostServiceTest.cs | 2 +- .../Timeline.Tests/Services/TimelineServiceTest.cs | 2 +- .../Timeline/Entities/HighlightTimelineEntity.cs | 8 +++-- BackEnd/Timeline/Models/TimelineInfo.cs | 4 +-- BackEnd/Timeline/Models/UserInfo.cs | 2 +- .../Timeline/Services/HighlightTimelineService.cs | 6 ++-- BackEnd/Timeline/Services/UserPermissionService.cs | 33 ++++++++++++++++++-- BackEnd/Timeline/Services/UserService.cs | 6 ++-- 10 files changed, 79 insertions(+), 21 deletions(-) (limited to 'BackEnd/Timeline/Entities') diff --git a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs index 74db74aa..a71c2208 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs @@ -35,7 +35,7 @@ namespace Timeline.Tests.Helpers if (_createUser) { var passwordService = new PasswordService(); - var userService = new UserService(NullLogger.Instance, context, passwordService, new Clock(), new UserPermissionService(context)); + var userService = new UserService(NullLogger.Instance, context, passwordService, new UserPermissionService(context), new Clock()); var admin = await userService.CreateUser("admin", "adminpw"); await userService.ModifyUser(admin.Id, new ModifyUserParams() { Nickname = "administrator" }); diff --git a/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs index a4cd983d..8ba26613 100644 --- a/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs @@ -1,18 +1,43 @@ -using Timeline.Services; +using FluentAssertions; +using Microsoft.Extensions.Logging.Abstractions; +using System.Threading.Tasks; +using Timeline.Services; +using Timeline.Tests.Helpers; +using Xunit; namespace Timeline.Tests.Services { public class HighlightTimelineServiceTest : DatabaseBasedTest { - private UserService _userService; - private TimelineService _timelineService; + private readonly TestClock _clock = new TestClock(); + private UserService _userService = default!; + private TimelineService _timelineService = default!; - private HighlightTimelineService _service; + private HighlightTimelineService _service = default!; protected override void OnDatabaseCreated() { - + _userService = new UserService(NullLogger.Instance, Database, new PasswordService(), new UserPermissionService(Database), _clock); + _timelineService = new TimelineService(Database, _userService, _clock); + _service = new HighlightTimelineService(Database, _userService, _timelineService, _clock); } + [Fact] + public async Task Should_Work() + { + { + var ht = await _service.GetHighlightTimelines(); + ht.Should().BeEmpty(); + } + + var userId = await _userService.GetUserIdByUsername("user"); + await _timelineService.CreateTimeline("tl", userId); + await _service.AddHighlightTimeline("tl", userId); + + { + var ht = await _service.GetHighlightTimelines(); + ht.Should().HaveCount(1).And.BeEquivalentTo(await _timelineService.GetTimeline("tl")); + } + } } } diff --git a/BackEnd/Timeline.Tests/Services/TimelinePostServiceTest.cs b/BackEnd/Timeline.Tests/Services/TimelinePostServiceTest.cs index 97512be5..7771ae0b 100644 --- a/BackEnd/Timeline.Tests/Services/TimelinePostServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/TimelinePostServiceTest.cs @@ -36,7 +36,7 @@ namespace Timeline.Tests.Services { _dataManager = new DataManager(Database, _eTagGenerator); _userPermissionService = new UserPermissionService(Database); - _userService = new UserService(NullLogger.Instance, Database, _passwordService, _clock, _userPermissionService); + _userService = new UserService(NullLogger.Instance, Database, _passwordService, _userPermissionService, _clock); _timelineService = new TimelineService(Database, _userService, _clock); _timelinePostService = new TimelinePostService(NullLogger.Instance, Database, _timelineService, _userService, _dataManager, _imageValidator, _clock); _userDeleteService = new UserDeleteService(NullLogger.Instance, Database, _timelinePostService); diff --git a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs index 98f03066..70f54ede 100644 --- a/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/TimelineServiceTest.cs @@ -26,7 +26,7 @@ namespace Timeline.Tests.Services protected override void OnDatabaseCreated() { _userPermissionService = new UserPermissionService(Database); - _userService = new UserService(NullLogger.Instance, Database, _passwordService, _clock, _userPermissionService); + _userService = new UserService(NullLogger.Instance, Database, _passwordService, _userPermissionService, _clock); _timelineService = new TimelineService(Database, _userService, _clock); } diff --git a/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs b/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs index 0a38c8a6..3378a175 100644 --- a/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs +++ b/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs @@ -1,10 +1,11 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace Timeline.Entities { [Table("highlight_timelines")] - public record HighlightTimelineEntity + public class HighlightTimelineEntity { [Key, Column("id"), DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; set; } @@ -20,5 +21,8 @@ namespace Timeline.Entities [ForeignKey(nameof(OperatorId))] public UserEntity? Operator { get; set; } + + [Column("add_time")] + public DateTime AddTime { get; set; } } } diff --git a/BackEnd/Timeline/Models/TimelineInfo.cs b/BackEnd/Timeline/Models/TimelineInfo.cs index 440f6b81..649af274 100644 --- a/BackEnd/Timeline/Models/TimelineInfo.cs +++ b/BackEnd/Timeline/Models/TimelineInfo.cs @@ -50,7 +50,7 @@ namespace Timeline.Models public string DataTag { get; set; } } - public record TimelinePostInfo + public class TimelinePostInfo { public TimelinePostInfo() { @@ -76,7 +76,7 @@ namespace Timeline.Models public string TimelineName { get; set; } = default!; } - public record TimelineInfo + public class TimelineInfo { public TimelineInfo() { diff --git a/BackEnd/Timeline/Models/UserInfo.cs b/BackEnd/Timeline/Models/UserInfo.cs index 058cc590..e8d57def 100644 --- a/BackEnd/Timeline/Models/UserInfo.cs +++ b/BackEnd/Timeline/Models/UserInfo.cs @@ -3,7 +3,7 @@ using Timeline.Services; namespace Timeline.Models { - public record UserInfo + public class UserInfo { public UserInfo() { diff --git a/BackEnd/Timeline/Services/HighlightTimelineService.cs b/BackEnd/Timeline/Services/HighlightTimelineService.cs index 619bc33e..0f4e5488 100644 --- a/BackEnd/Timeline/Services/HighlightTimelineService.cs +++ b/BackEnd/Timeline/Services/HighlightTimelineService.cs @@ -46,12 +46,14 @@ namespace Timeline.Services private readonly DatabaseContext _database; private readonly IBasicUserService _userService; private readonly ITimelineService _timelineService; + private readonly IClock _clock; - public HighlightTimelineService(DatabaseContext database, IBasicUserService userService, ITimelineService timelineService) + public HighlightTimelineService(DatabaseContext database, IBasicUserService userService, ITimelineService timelineService, IClock clock) { _database = database; _userService = userService; _timelineService = timelineService; + _clock = clock; } public async Task AddHighlightTimeline(string timelineName, long? operatorId) @@ -70,7 +72,7 @@ namespace Timeline.Services if (alreadyIs) return; - _database.HighlightTimelines.Add(new HighlightTimelineEntity { TimelineId = timelineId, OperatorId = operatorId }); + _database.HighlightTimelines.Add(new HighlightTimelineEntity { TimelineId = timelineId, OperatorId = operatorId, AddTime = _clock.GetCurrentTime() }); await _database.SaveChangesAsync(); } diff --git a/BackEnd/Timeline/Services/UserPermissionService.cs b/BackEnd/Timeline/Services/UserPermissionService.cs index 9683000a..bd7cd6aa 100644 --- a/BackEnd/Timeline/Services/UserPermissionService.cs +++ b/BackEnd/Timeline/Services/UserPermissionService.cs @@ -28,7 +28,7 @@ namespace Timeline.Services /// /// Represents a user's permissions. /// - public class UserPermissions : IEnumerable + public class UserPermissions : IEnumerable, IEquatable { public static UserPermissions AllPermissions { get; } = new UserPermissions(Enum.GetValues()); @@ -49,10 +49,10 @@ namespace Timeline.Services public UserPermissions(IEnumerable permissions) { if (permissions == null) throw new ArgumentNullException(nameof(permissions)); - _permissions = new HashSet(permissions); + _permissions = new SortedSet(permissions); } - private readonly HashSet _permissions = new(); + private readonly SortedSet _permissions = new(); /// /// Check if a permission is contained in the list. @@ -108,6 +108,33 @@ namespace Timeline.Services { return ((IEnumerable)_permissions).GetEnumerator(); } + + public bool Equals(UserPermissions? other) + { + if (other == null) + return false; + + return _permissions.SequenceEqual(other._permissions); + } + + public override bool Equals(object? obj) + { + return Equals(obj as UserPermissions); + } + + public override int GetHashCode() + { + int result = 0; + foreach (var permission in Enum.GetValues()) + { + if (_permissions.Contains(permission)) + { + result += 1; + } + result <<= 1; + } + return result; + } } public interface IUserPermissionService diff --git a/BackEnd/Timeline/Services/UserService.cs b/BackEnd/Timeline/Services/UserService.cs index 96068e44..c99e86b0 100644 --- a/BackEnd/Timeline/Services/UserService.cs +++ b/BackEnd/Timeline/Services/UserService.cs @@ -17,7 +17,7 @@ namespace Timeline.Services /// /// Null means not change. /// - public record ModifyUserParams + public class ModifyUserParams { public string? Username { get; set; } public string? Password { get; set; } @@ -78,13 +78,13 @@ namespace Timeline.Services private readonly UsernameValidator _usernameValidator = new UsernameValidator(); private readonly NicknameValidator _nicknameValidator = new NicknameValidator(); - public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService, IClock clock, IUserPermissionService userPermissionService) : base(databaseContext) + public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService, IUserPermissionService userPermissionService, IClock clock) : base(databaseContext) { _logger = logger; - _clock = clock; _databaseContext = databaseContext; _passwordService = passwordService; _userPermissionService = userPermissionService; + _clock = clock; } private void CheckUsernameFormat(string username, string? paramName) -- cgit v1.2.3 From d934c1273bc20533683eaad858a1c499c7729a28 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 17 Dec 2020 20:08:33 +0800 Subject: ... --- BackEnd/Timeline.Tests/Helpers/TestDatabase.cs | 13 +- .../Timeline.Tests/Services/DatabaseBasedTest.cs | 10 +- .../Services/HighlightTimelineServiceTest.cs | 49 +++ .../Timeline/Entities/HighlightTimelineEntity.cs | 3 + ...0201217093401_AddHighlightTimelines.Designer.cs | 451 +++++++++++++++++++++ .../20201217093401_AddHighlightTimelines.cs | 55 +++ .../Migrations/DatabaseContextModelSnapshot.cs | 49 +++ .../Timeline/Services/HighlightTimelineService.cs | 85 +++- 8 files changed, 706 insertions(+), 9 deletions(-) create mode 100644 BackEnd/Timeline/Migrations/20201217093401_AddHighlightTimelines.Designer.cs create mode 100644 BackEnd/Timeline/Migrations/20201217093401_AddHighlightTimelines.cs (limited to 'BackEnd/Timeline/Entities') diff --git a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs index a71c2208..00164835 100644 --- a/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs +++ b/BackEnd/Timeline.Tests/Helpers/TestDatabase.cs @@ -1,11 +1,14 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using System; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Migrations; using Timeline.Services; using Xunit; +using Xunit.Abstractions; namespace Timeline.Tests.Helpers { @@ -54,12 +57,14 @@ namespace Timeline.Tests.Helpers public SqliteConnection Connection { get; } - public DatabaseContext CreateContext() + public DatabaseContext CreateContext(ITestOutputHelper? testOutputHelper = null) { - var options = new DbContextOptionsBuilder() - .UseSqlite(Connection).Options; + var optionsBuilder = new DbContextOptionsBuilder() + .UseSqlite(Connection); - return new DatabaseContext(options); + if (testOutputHelper != null) optionsBuilder.LogTo(testOutputHelper.WriteLine).EnableDetailedErrors().EnableSensitiveDataLogging(); + + return new DatabaseContext(optionsBuilder.Options); } } } diff --git a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs index 3bb6ebb5..90fb6463 100644 --- a/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs +++ b/BackEnd/Timeline.Tests/Services/DatabaseBasedTest.cs @@ -2,6 +2,7 @@ using Timeline.Entities; using Timeline.Tests.Helpers; using Xunit; +using Xunit.Abstractions; namespace Timeline.Tests.Services { @@ -10,15 +11,20 @@ namespace Timeline.Tests.Services protected TestDatabase TestDatabase { get; } protected DatabaseContext Database { get; private set; } = default!; - protected DatabaseBasedTest(bool databaseCreateUsers = true) + private readonly ITestOutputHelper? _testOutputHelper; + + protected DatabaseBasedTest(bool databaseCreateUsers = true, ITestOutputHelper? testOutputHelper = null) { + _testOutputHelper = testOutputHelper; TestDatabase = new TestDatabase(databaseCreateUsers); } + protected DatabaseBasedTest(ITestOutputHelper? testOutputHelper) : this(true, testOutputHelper) { } + public async Task InitializeAsync() { await TestDatabase.InitializeAsync(); - Database = TestDatabase.CreateContext(); + Database = TestDatabase.CreateContext(_testOutputHelper); await OnDatabaseCreatedAsync(); OnDatabaseCreated(); } diff --git a/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs index 8ba26613..dca070c6 100644 --- a/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs +++ b/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Timeline.Services; using Timeline.Tests.Helpers; using Xunit; +using Xunit.Abstractions; namespace Timeline.Tests.Services { @@ -15,6 +16,12 @@ namespace Timeline.Tests.Services private HighlightTimelineService _service = default!; + public HighlightTimelineServiceTest(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + + } + protected override void OnDatabaseCreated() { _userService = new UserService(NullLogger.Instance, Database, new PasswordService(), new UserPermissionService(Database), _clock); @@ -39,5 +46,47 @@ namespace Timeline.Tests.Services ht.Should().HaveCount(1).And.BeEquivalentTo(await _timelineService.GetTimeline("tl")); } } + + [Fact] + public async Task NewOne_Should_BeAtLast() + { + var userId = await _userService.GetUserIdByUsername("user"); + await _timelineService.CreateTimeline("t1", userId); + await _service.AddHighlightTimeline("t1", userId); + + await _timelineService.CreateTimeline("t2", userId); + await _service.AddHighlightTimeline("t2", userId); + + var ht = await _service.GetHighlightTimelines(); + + ht.Should().HaveCount(2); + ht[0].Name.Should().Be("t1"); + ht[1].Name.Should().Be("t2"); + } + + [Fact] + public async Task Multiple_Should_Work() + { + var userId = await _userService.GetUserIdByUsername("user"); + await _timelineService.CreateTimeline("t1", userId); + await _service.AddHighlightTimeline("t1", userId); + + await _timelineService.CreateTimeline("t2", userId); + await _service.AddHighlightTimeline("t2", userId); + + await _timelineService.CreateTimeline("t3", userId); + await _service.AddHighlightTimeline("t3", userId); + + await _service.MoveHighlightTimeline("t3", 2); + (await _service.GetHighlightTimelines())[1].Name.Should().Be("t3"); + + await _service.MoveHighlightTimeline("t1", 3); + (await _service.GetHighlightTimelines())[2].Name.Should().Be("t1"); + + await _service.RemoveHighlightTimeline("t2", userId); + await _service.RemoveHighlightTimeline("t1", userId); + await _service.RemoveHighlightTimeline("t3", userId); + (await _service.GetHighlightTimelines()).Should().BeEmpty(); + } } } diff --git a/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs b/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs index 3378a175..35bf6af3 100644 --- a/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs +++ b/BackEnd/Timeline/Entities/HighlightTimelineEntity.cs @@ -24,5 +24,8 @@ namespace Timeline.Entities [Column("add_time")] public DateTime AddTime { get; set; } + + [Column("order")] + public long Order { get; set; } } } diff --git a/BackEnd/Timeline/Migrations/20201217093401_AddHighlightTimelines.Designer.cs b/BackEnd/Timeline/Migrations/20201217093401_AddHighlightTimelines.Designer.cs new file mode 100644 index 00000000..6cc591fa --- /dev/null +++ b/BackEnd/Timeline/Migrations/20201217093401_AddHighlightTimelines.Designer.cs @@ -0,0 +1,451 @@ +// +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("20201217093401_AddHighlightTimelines")] + partial class AddHighlightTimelines + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("Timeline.Entities.DataEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Ref") + .HasColumnType("INTEGER") + .HasColumnName("ref"); + + b.Property("Tag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tag"); + + b.HasKey("Id"); + + b.HasIndex("Tag") + .IsUnique(); + + b.ToTable("data"); + }); + + modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("AddTime") + .HasColumnType("TEXT") + .HasColumnName("add_time"); + + b.Property("OperatorId") + .HasColumnType("INTEGER") + .HasColumnName("operator_id"); + + b.Property("Order") + .HasColumnType("INTEGER") + .HasColumnName("order"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Key") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("key"); + + b.HasKey("Id"); + + b.ToTable("jwt_token"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("CreateTime") + .HasColumnType("TEXT") + .HasColumnName("create_time"); + + b.Property("CurrentPostLocalId") + .HasColumnType("INTEGER") + .HasColumnName("current_post_local_id"); + + b.Property("Description") + .HasColumnType("TEXT") + .HasColumnName("description"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("NameLastModified") + .HasColumnType("TEXT") + .HasColumnName("name_last_modified"); + + b.Property("OwnerId") + .HasColumnType("INTEGER") + .HasColumnName("owner"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Visibility") + .HasColumnType("INTEGER") + .HasColumnName("visibility"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.ToTable("timelines"); + }); + + modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("TimelineId") + .HasColumnType("INTEGER") + .HasColumnName("timeline"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("INTEGER") + .HasColumnName("author"); + + b.Property("Content") + .HasColumnType("TEXT") + .HasColumnName("content"); + + b.Property("ContentType") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("content_type"); + + b.Property("ExtraContent") + .HasColumnType("TEXT") + .HasColumnName("extra_content"); + + b.Property("LastUpdated") + .HasColumnType("TEXT") + .HasColumnName("last_updated"); + + b.Property("LocalId") + .HasColumnType("INTEGER") + .HasColumnName("local_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("DataTag") + .HasColumnType("TEXT") + .HasColumnName("data_tag"); + + b.Property("LastModified") + .HasColumnType("TEXT") + .HasColumnName("last_modified"); + + b.Property("Type") + .HasColumnType("TEXT") + .HasColumnName("type"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("user"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("user_avatars"); + }); + + modelBuilder.Entity("Timeline.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("CreateTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("create_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("LastModified") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("last_modified") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("Nickname") + .HasColumnType("TEXT") + .HasColumnName("nickname"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("password"); + + b.Property("UniqueId") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("unique_id") + .HasDefaultValueSql("lower(hex(randomblob(16)))"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("username"); + + b.Property("UsernameChangeTime") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("username_change_time") + .HasDefaultValueSql("datetime('now', 'utc')"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Permission") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("permission"); + + b.Property("UserId") + .HasColumnType("INTEGER") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("user_permission"); + }); + + 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/20201217093401_AddHighlightTimelines.cs b/BackEnd/Timeline/Migrations/20201217093401_AddHighlightTimelines.cs new file mode 100644 index 00000000..e838615e --- /dev/null +++ b/BackEnd/Timeline/Migrations/20201217093401_AddHighlightTimelines.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Timeline.Migrations +{ + public partial class AddHighlightTimelines : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "highlight_timelines", + columns: table => new + { + id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + timeline_id = table.Column(type: "INTEGER", nullable: false), + operator_id = table.Column(type: "INTEGER", nullable: true), + add_time = table.Column(type: "TEXT", nullable: false), + order = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_highlight_timelines", x => x.id); + table.ForeignKey( + name: "FK_highlight_timelines_timelines_timeline_id", + column: x => x.timeline_id, + principalTable: "timelines", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_highlight_timelines_users_operator_id", + column: x => x.operator_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_highlight_timelines_operator_id", + table: "highlight_timelines", + column: "operator_id"); + + migrationBuilder.CreateIndex( + name: "IX_highlight_timelines_timeline_id", + table: "highlight_timelines", + column: "timeline_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "highlight_timelines"); + } + } +} diff --git a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs index 2f0f75a2..ea3378dc 100644 --- a/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs +++ b/BackEnd/Timeline/Migrations/DatabaseContextModelSnapshot.cs @@ -45,6 +45,38 @@ namespace Timeline.Migrations b.ToTable("data"); }); + modelBuilder.Entity("Timeline.Entities.HighlightTimelineEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("AddTime") + .HasColumnType("TEXT") + .HasColumnName("add_time"); + + b.Property("OperatorId") + .HasColumnType("INTEGER") + .HasColumnName("operator_id"); + + b.Property("Order") + .HasColumnType("INTEGER") + .HasColumnName("order"); + + b.Property("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("Id") @@ -306,6 +338,23 @@ namespace Timeline.Migrations b.ToTable("user_permission"); }); + 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") diff --git a/BackEnd/Timeline/Services/HighlightTimelineService.cs b/BackEnd/Timeline/Services/HighlightTimelineService.cs index 0f4e5488..ea3e4c7e 100644 --- a/BackEnd/Timeline/Services/HighlightTimelineService.cs +++ b/BackEnd/Timeline/Services/HighlightTimelineService.cs @@ -9,10 +9,25 @@ using Timeline.Services.Exceptions; namespace Timeline.Services { + + [Serializable] + public class InvalidHighlightTimelineException : Exception + { + public InvalidHighlightTimelineException() { } + public InvalidHighlightTimelineException(string message) : base(message) { } + public InvalidHighlightTimelineException(string message, Exception inner) : base(message, inner) { } + protected InvalidHighlightTimelineException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } + + /// + /// Service that controls highlight timeline. + /// public interface IHighlightTimelineService { /// - /// Get all highlight timelines. + /// Get all highlight timelines in order. /// /// A list of all highlight timelines. Task> GetHighlightTimelines(); @@ -39,6 +54,21 @@ namespace Timeline.Services /// Thrown when timeline with given name does not exist. /// Thrown when user with given operator id does not exist. Task RemoveHighlightTimeline(string timelineName, long? operatorId); + + /// + /// Move a highlight timeline to a new position. + /// + /// The timeline name. + /// The new position. Starts at 1. + /// Thrown when is null. + /// Thrown when is not a valid timeline name. + /// Thrown when timeline with given name does not exist. + /// Thrown when given timeline is not a highlight timeline. + /// + /// If is smaller than 1. Then move the timeline to head. + /// If is bigger than total count. Then move the timeline to tail. + /// + Task MoveHighlightTimeline(string timelineName, long newPosition); } public class HighlightTimelineService : IHighlightTimelineService @@ -72,13 +102,13 @@ namespace Timeline.Services if (alreadyIs) return; - _database.HighlightTimelines.Add(new HighlightTimelineEntity { TimelineId = timelineId, OperatorId = operatorId, AddTime = _clock.GetCurrentTime() }); + _database.HighlightTimelines.Add(new HighlightTimelineEntity { TimelineId = timelineId, OperatorId = operatorId, AddTime = _clock.GetCurrentTime(), Order = await _database.HighlightTimelines.CountAsync() + 1 }); await _database.SaveChangesAsync(); } public async Task> GetHighlightTimelines() { - var entities = await _database.HighlightTimelines.Select(t => new { t.Id }).ToListAsync(); + var entities = await _database.HighlightTimelines.OrderBy(t => t.Order).Select(t => new { t.Id }).ToListAsync(); var result = new List(); @@ -106,10 +136,59 @@ namespace Timeline.Services if (entity == null) return false; + await using var transaction = await _database.Database.BeginTransactionAsync(); + + var order = entity.Order; + _database.HighlightTimelines.Remove(entity); await _database.SaveChangesAsync(); + await _database.Database.ExecuteSqlRawAsync("UPDATE highlight_timelines SET `order` = `order` - 1 WHERE `order` > {0}", order); + + await transaction.CommitAsync(); + return true; } + + public async Task MoveHighlightTimeline(string timelineName, long newPosition) + { + if (timelineName == null) + throw new ArgumentNullException(nameof(timelineName)); + + var timelineId = await _timelineService.GetTimelineIdByName(timelineName); + + var entity = await _database.HighlightTimelines.SingleOrDefaultAsync(t => t.TimelineId == timelineId); + + if (entity == null) throw new InvalidHighlightTimelineException("You can't move a non-highlight timeline."); + + var oldPosition = entity.Order; + + if (newPosition < 1) + { + newPosition = 1; + } + else + { + var totalCount = await _database.HighlightTimelines.CountAsync(); + if (newPosition > totalCount) newPosition = totalCount; + } + + if (oldPosition == newPosition) return; + + await using var transaction = await _database.Database.BeginTransactionAsync(); + + if (newPosition > oldPosition) + { + await _database.Database.ExecuteSqlRawAsync("UPDATE highlight_timelines SET `order` = `order` - 1 WHERE `order` BETWEEN {0} AND {1}", oldPosition + 1, newPosition); + await _database.Database.ExecuteSqlRawAsync("UPDATE highlight_timelines SET `order` = {0} WHERE id = {1}", newPosition, entity.Id); + } + else + { + await _database.Database.ExecuteSqlRawAsync("UPDATE highlight_timelines SET `order` = `order` + 1 WHERE `order` BETWEEN {0} AND {1}", newPosition, oldPosition - 1); + await _database.Database.ExecuteSqlRawAsync("UPDATE highlight_timelines SET `order` = {0} WHERE id = {1}", newPosition, entity.Id); + } + + await transaction.CommitAsync(); + } } } -- cgit v1.2.3