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
{
[Serializable]
public class InvalidBookmarkException : Exception
{
public InvalidBookmarkException() { }
public InvalidBookmarkException(string message) : base(message) { }
public InvalidBookmarkException(string message, Exception inner) : base(message, inner) { }
protected InvalidBookmarkException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
///
/// Service interface that manages timeline bookmarks.
///
public interface IBookmarkTimelineService
{
///
/// Get bookmarks of a user.
///
/// User id of bookmark owner.
/// Id of Bookmark timelines in order.
/// Thrown when user does not exist.
Task> GetBookmarks(long userId);
///
/// Check if a timeline is a bookmark.
///
/// The user id.
/// Timeline id.
/// If true it will throw when user does not exist.
/// If true it will throw when timeline does not exist.
/// True if timeline is a bookmark. Otherwise false.
/// Throw if user does not exist and is true.
/// Thrown if timeline does not exist and is true.
Task IsBookmark(long userId, long timelineId, bool checkUserExistence = true, bool checkTimelineExistence = true);
///
/// Add a bookmark to tail to a user.
///
/// User id of bookmark owner.
/// Timeline id.
/// True if timeline is added to bookmark. False if it already is.
/// Thrown when user does not exist.
/// Thrown when timeline does not exist.
Task AddBookmark(long userId, long timelineId);
///
/// Remove a bookmark from a user.
///
/// User id of bookmark owner.
/// Timeline id.
/// True if deletion is performed. False if bookmark does not exist.
/// Thrown when user does not exist.
/// Thrown when timeline does not exist.
Task RemoveBookmark(long userId, long timelineId);
///
/// Move bookmark to a new position.
///
/// User id of bookmark owner.
/// Timeline name.
/// New position. Starts at 1.
/// Thrown when user does not exist.
/// Thrown when timeline does not exist.
/// Thrown when the timeline is not a bookmark.
Task MoveBookmark(long userId, long timelineId, long newPosition);
}
public class BookmarkTimelineService : IBookmarkTimelineService
{
private readonly DatabaseContext _database;
private readonly IBasicUserService _userService;
private readonly IBasicTimelineService _timelineService;
public BookmarkTimelineService(DatabaseContext database, IBasicUserService userService, IBasicTimelineService timelineService)
{
_database = database;
_userService = userService;
_timelineService = timelineService;
}
public async Task AddBookmark(long userId, long timelineId)
{
if (!await _userService.CheckUserExistence(userId))
throw new UserNotExistException(userId);
if (!await _timelineService.CheckExistence(timelineId))
throw new TimelineNotExistException(timelineId);
if (await _database.BookmarkTimelines.AnyAsync(t => t.TimelineId == timelineId && t.UserId == userId))
return false;
_database.BookmarkTimelines.Add(new BookmarkTimelineEntity
{
TimelineId = timelineId,
UserId = userId,
Rank = (await _database.BookmarkTimelines.CountAsync(t => t.UserId == userId)) + 1
});
await _database.SaveChangesAsync();
return true;
}
public async Task> GetBookmarks(long userId)
{
if (!await _userService.CheckUserExistence(userId))
throw new UserNotExistException(userId);
var entities = await _database.BookmarkTimelines.Where(t => t.UserId == userId).OrderBy(t => t.Rank).Select(t => new { t.TimelineId }).ToListAsync();
return entities.Select(e => e.TimelineId).ToList();
}
public async Task IsBookmark(long userId, long timelineId, bool checkUserExistence = true, bool checkTimelineExistence = true)
{
if (checkUserExistence && !await _userService.CheckUserExistence(userId))
throw new UserNotExistException(userId);
if (checkTimelineExistence && !await _timelineService.CheckExistence(timelineId))
throw new TimelineNotExistException(timelineId);
return await _database.BookmarkTimelines.AnyAsync(b => b.TimelineId == timelineId && b.UserId == userId);
}
public async Task MoveBookmark(long userId, long timelineId, long newPosition)
{
if (!await _userService.CheckUserExistence(userId))
throw new UserNotExistException(userId);
if (!await _timelineService.CheckExistence(timelineId))
throw new TimelineNotExistException(timelineId);
var entity = await _database.BookmarkTimelines.SingleOrDefaultAsync(t => t.TimelineId == timelineId && t.UserId == userId);
if (entity == null) throw new InvalidBookmarkException("You can't move a non-bookmark timeline.");
var oldPosition = entity.Rank;
if (newPosition < 1)
{
newPosition = 1;
}
else
{
var totalCount = await _database.BookmarkTimelines.CountAsync(t => t.UserId == userId);
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 `bookmark_timelines` SET `rank` = `rank` - 1 WHERE `rank` BETWEEN {0} AND {1} AND `user` = {2}", oldPosition + 1, newPosition, userId);
await _database.Database.ExecuteSqlRawAsync("UPDATE `bookmark_timelines` SET `rank` = {0} WHERE `id` = {1}", newPosition, entity.Id);
}
else
{
await _database.Database.ExecuteSqlRawAsync("UPDATE `bookmark_timelines` SET `rank` = `rank` + 1 WHERE `rank` BETWEEN {0} AND {1} AND `user` = {2}", newPosition, oldPosition - 1, userId);
await _database.Database.ExecuteSqlRawAsync("UPDATE `bookmark_timelines` SET `rank` = {0} WHERE `id` = {1}", newPosition, entity.Id);
}
await transaction.CommitAsync();
}
public async Task RemoveBookmark(long userId, long timelineId)
{
if (!await _userService.CheckUserExistence(userId))
throw new UserNotExistException(userId);
if (!await _timelineService.CheckExistence(timelineId))
throw new TimelineNotExistException(timelineId);
var entity = await _database.BookmarkTimelines.SingleOrDefaultAsync(t => t.UserId == userId && t.TimelineId == timelineId);
if (entity == null) return false;
await using var transaction = await _database.Database.BeginTransactionAsync();
var rank = entity.Rank;
_database.BookmarkTimelines.Remove(entity);
await _database.SaveChangesAsync();
await _database.Database.ExecuteSqlRawAsync("UPDATE `bookmark_timelines` SET `rank` = `rank` - 1 WHERE `rank` > {0}", rank);
await transaction.CommitAsync();
return true;
}
}
}