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. --- .../Services/HighlightTimelineServiceTest.cs | 22 ++++ BackEnd/Timeline/Entities/DatabaseContext.cs | 2 + .../Timeline/Entities/HighlightTimelineEntity.cs | 24 +++++ .../Exceptions/TimelineNotExistException.cs | 7 +- .../Timeline/Services/HighlightTimelineService.cs | 112 +++++++++++++++++++++ BackEnd/Timeline/Services/TimelineService.cs | 43 ++++++++ BackEnd/Timeline/Services/UserService.cs | 12 +++ 7 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs create mode 100644 BackEnd/Timeline/Entities/HighlightTimelineEntity.cs create mode 100644 BackEnd/Timeline/Services/HighlightTimelineService.cs diff --git a/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs b/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs new file mode 100644 index 00000000..950fa974 --- /dev/null +++ b/BackEnd/Timeline.Tests/Services/HighlightTimelineServiceTest.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Services; + +namespace Timeline.Tests.Services +{ + public class HighlightTimelineServiceTest : DatabaseBasedTest + { + private UserService _userService; + private TimelineService _timelineService; + + private HighlightTimelineService _service; + + protected override void OnDatabaseCreated() + { + + } + + } +} 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; } + } +} diff --git a/BackEnd/Timeline/Services/Exceptions/TimelineNotExistException.cs b/BackEnd/Timeline/Services/Exceptions/TimelineNotExistException.cs index 70970b24..ef882ffe 100644 --- a/BackEnd/Timeline/Services/Exceptions/TimelineNotExistException.cs +++ b/BackEnd/Timeline/Services/Exceptions/TimelineNotExistException.cs @@ -6,7 +6,11 @@ namespace Timeline.Services.Exceptions [Serializable] public class TimelineNotExistException : EntityNotExistException { - public TimelineNotExistException() : this(null, null) { } + public TimelineNotExistException() : this((long?)null) { } + public TimelineNotExistException(long? id) : this(id, null) { } + public TimelineNotExistException(long? id, Exception? inner) : this(id, null, inner) { } + public TimelineNotExistException(long? id, string? message, Exception? inner) : base(EntityNames.Timeline, null, message, inner) { TimelineId = id; } + public TimelineNotExistException(string? timelineName) : this(timelineName, null) { } public TimelineNotExistException(string? timelineName, Exception? inner) : this(timelineName, null, inner) { } public TimelineNotExistException(string? timelineName, string? message, Exception? inner = null) @@ -17,5 +21,6 @@ namespace Timeline.Services.Exceptions System.Runtime.Serialization.StreamingContext context) : base(info, context) { } public string? TimelineName { get; set; } + public long? TimelineId { get; set; } } } diff --git a/BackEnd/Timeline/Services/HighlightTimelineService.cs b/BackEnd/Timeline/Services/HighlightTimelineService.cs new file mode 100644 index 00000000..7528d9b0 --- /dev/null +++ b/BackEnd/Timeline/Services/HighlightTimelineService.cs @@ -0,0 +1,112 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Entities; +using Timeline.Services.Exceptions; + +namespace Timeline.Services +{ + public interface IHighlightTimelineService + { + /// + /// Get all highlight timelines. + /// + /// A list of all highlight timelines. + Task> GetHighlightTimelines(); + + /// + /// Add a timeline to highlight list. + /// + /// The timeline name. + /// The user id of operator. + /// Thrown when is null. + /// Thrown when is not a valid timeline name. + /// Thrown when timeline with given name does not exist. + /// Thrown when user with given operator id does not exist. + Task AddHighlightTimeline(string timelineName, long? operatorId); + + /// + /// Remove a timeline from highlight list. + /// + /// The timeline name. + /// The user id of operator. + /// True if deletion is actually performed. Otherwise false (timeline was not in the list). + /// Thrown when is null. + /// Thrown when is not a valid timeline name. + /// 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); + } + + public class HighlightTimelineService : IHighlightTimelineService + { + private readonly DatabaseContext _database; + private readonly IUserService _userService; + private readonly ITimelineService _timelineService; + + public HighlightTimelineService(DatabaseContext database, IUserService userService, ITimelineService timelineService) + { + _database = database; + _userService = userService; + _timelineService = timelineService; + } + + public async Task AddHighlightTimeline(string timelineName, long? operatorId) + { + if (timelineName == null) + throw new ArgumentNullException(nameof(timelineName)); + + var timelineId = await _timelineService.GetTimelineIdByName(timelineName); + + if (operatorId.HasValue && !await _userService.CheckUserExistence(operatorId.Value)) + { + throw new UserNotExistException(null, operatorId.Value, "User with given operator id does not exist.", null); + } + + var alreadyIs = await _database.HighlightTimelines.AnyAsync(t => t.TimelineId == timelineId); + + if (alreadyIs) return; + + _database.HighlightTimelines.Add(new HighlightTimelineEntity { TimelineId = timelineId, OperatorId = operatorId }); + await _database.SaveChangesAsync(); + } + + public async Task> GetHighlightTimelines() + { + var entities = await _database.HighlightTimelines.Select(t => new { t.Id }).ToListAsync(); + + var result = new List(); + + foreach (var entity in entities) + { + result.Add(await _timelineService.GetTimelineById(entity.Id)); + } + + return result; + } + + public async Task RemoveHighlightTimeline(string timelineName, long? operatorId) + { + if (timelineName == null) + throw new ArgumentNullException(nameof(timelineName)); + + var timelineId = await _timelineService.GetTimelineIdByName(timelineName); + + if (operatorId.HasValue && !await _userService.CheckUserExistence(operatorId.Value)) + { + throw new UserNotExistException(null, operatorId.Value, "User with given operator id does not exist.", null); + } + + var entity = await _database.HighlightTimelines.SingleOrDefaultAsync(t => t.TimelineId == timelineId); + + if (entity == null) return false; + + _database.HighlightTimelines.Remove(entity); + await _database.SaveChangesAsync(); + + return true; + } + } +} diff --git a/BackEnd/Timeline/Services/TimelineService.cs b/BackEnd/Timeline/Services/TimelineService.cs index 769e8bed..f8c729bf 100644 --- a/BackEnd/Timeline/Services/TimelineService.cs +++ b/BackEnd/Timeline/Services/TimelineService.cs @@ -79,6 +79,19 @@ namespace Timeline.Services /// Task GetTimelineLastModifiedTime(string timelineName); + /// + /// Get the timeline id by name. + /// + /// Timeline name. + /// Id of the timeline. + /// Thrown when is null. + /// Throw when is of bad format. + /// + /// Thrown when timeline with name does not exist. + /// If it is a personal timeline, then inner exception is . + /// + Task GetTimelineIdByName(string timelineName); + /// /// Get the timeline unique id. /// @@ -105,6 +118,14 @@ namespace Timeline.Services /// Task GetTimeline(string timelineName); + /// + /// Get timeline by id. + /// + /// Id of timeline. + /// The timeline. + /// Thrown when timeline with given id does not exist. + Task GetTimelineById(long id); + /// /// Set the properties of a timeline. /// @@ -572,6 +593,18 @@ namespace Timeline.Services return timelineEntity.UniqueId; } + public async Task GetTimelineIdByName(string timelineName) + { + if (timelineName == null) + throw new ArgumentNullException(nameof(timelineName)); + + var timelineId = await FindTimelineId(timelineName); + + var timelineEntity = await _database.Timelines.Where(t => t.Id == timelineId).Select(t => new { t.Id }).SingleAsync(); + + return timelineEntity.Id; + } + public async Task GetTimeline(string timelineName) { if (timelineName == null) @@ -584,6 +617,16 @@ namespace Timeline.Services return await MapTimelineFromEntity(timelineEntity); } + public async Task GetTimelineById(long id) + { + var timelineEntity = await _database.Timelines.Where(t => t.Id == id).Include(t => t.Members).SingleOrDefaultAsync(); + + if (timelineEntity is null) + throw new TimelineNotExistException(id); + + return await MapTimelineFromEntity(timelineEntity); + } + public async Task> GetPosts(string timelineName, DateTime? modifiedSince = null, bool includeDeleted = false) { modifiedSince = modifiedSince?.MyToUtc(); diff --git a/BackEnd/Timeline/Services/UserService.cs b/BackEnd/Timeline/Services/UserService.cs index 2c5644cd..76c24666 100644 --- a/BackEnd/Timeline/Services/UserService.cs +++ b/BackEnd/Timeline/Services/UserService.cs @@ -38,6 +38,13 @@ namespace Timeline.Services /// Thrown when password is wrong. Task VerifyCredential(string username, string password); + /// + /// Check if a user exists. + /// + /// The id of the user. + /// True if exists. Otherwise false. + Task CheckUserExistence(long id); + /// /// Try to get a user by id. /// @@ -188,6 +195,11 @@ namespace Timeline.Services return await CreateUserFromEntity(entity); } + public async Task CheckUserExistence(long id) + { + return await _databaseContext.Users.AnyAsync(u => u.Id == id); + } + public async Task GetUser(long id) { var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); -- cgit v1.2.3