using AutoMapper; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using YABA.Common.DTOs; using YABA.Common.DTOs.Bookmarks; using YABA.Common.DTOs.Tags; using YABA.Common.Extensions; using YABA.Common.Interfaces; using YABA.Common.Lookups; using YABA.Data.Context; using YABA.Data.Extensions; using YABA.Models; using YABA.Service.Interfaces; namespace YABA.Service { public class BookmarkService : IBookmarkService { private readonly YABAReadOnlyContext _roContext; private readonly YABAReadWriteContext _context; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IMapper _mapper; public BookmarkService( YABAReadOnlyContext roContext, YABAReadWriteContext context, IHttpContextAccessor httpContextAccessor, IMapper mapper) { _roContext = roContext; _context = context; _httpContextAccessor = httpContextAccessor; _mapper = mapper; } public CrudResultDTO> GetAll() { var currentUserId = GetCurrentUserId(); var userBookmarks = _roContext.Bookmarks.Where(x => x.UserId == currentUserId).ToDictionary(k => k.Id, v => v); var bookmarkTagsLookup = _roContext.BookmarkTags .Include(x => x.Tag) .Where(x => userBookmarks.Keys.Contains(x.BookmarkId)) .ToList() .GroupBy(x => x.BookmarkId) .ToDictionary(k => k.Key, v => v.ToList()); var userBookmarkDTOs = new List(); foreach(var bookmark in userBookmarks) { var bookmarkDTO = _mapper.Map(bookmark.Value); bookmarkTagsLookup.TryGetValue(bookmark.Key, out var bookmarkTags); if(bookmarkTags != null) { foreach (var bookmarkTag in bookmarkTags) { var tagDTO = _mapper.Map(bookmarkTag.Tag); bookmarkDTO.Tags.Add(tagDTO); } } userBookmarkDTOs.Add(bookmarkDTO); } return new CrudResultDTO> { Entry = userBookmarkDTOs, CrudResult = CrudResultLookup.RetrieveSuccessful }; } public async Task?> CreateBookmark(CreateBookmarkRequestDTO request) { var crudResult = new CrudResultDTO() { Entry = request, CrudResult = CrudResultLookup.CreateFailed }; var currentUserId = GetCurrentUserId(); if (!_roContext.Users.UserExists(currentUserId)) return crudResult; if(await _roContext.Bookmarks.AnyAsync(x => x.UserId == currentUserId && x.Url == request.Url)) { crudResult.CrudResult = CrudResultLookup.CreateFailedEntryExists; return crudResult; } var bookmark = _mapper.Map(request); bookmark.UserId = currentUserId; await _context.Bookmarks.AddAsync(bookmark); if (await _context.SaveChangesAsync() > 0) crudResult.CrudResult = CrudResultLookup.CreateSucceeded; return crudResult; } public async Task> UpdateBookmark(int id, UpdateBookmarkRequestDTO request) { var crudResult = new CrudResultDTO() { Entry = request, CrudResult = CrudResultLookup.UpdateFailed }; var currentUserId = GetCurrentUserId(); var bookmark = _context.Bookmarks.FirstOrDefault(x => x.UserId == currentUserId && x.Id == id); if(bookmark == null) return crudResult; bookmark.Title = request.Title; bookmark.Description = request.Description; bookmark.Note = request.Note; bookmark.IsHidden = request.IsHidden; if (await _context.SaveChangesAsync() > 0) crudResult.CrudResult = CrudResultLookup.UpdateSucceeded; return crudResult; } public async Task>> UpdateBookmarkTags(int id, IEnumerable tags) { var crudResults = tags.Select((x) => new CrudResultDTO { Entry = x, CrudResult = CrudResultLookup.UpdateFailed }).ToList(); var currentUserId = GetCurrentUserId(); if (!_roContext.Bookmarks.Any(x => x.Id == id && x.UserId == currentUserId)) return crudResults; // Add tags that are not yet in the database var savedUserTags = _context.Tags.Where(x => x.UserId == currentUserId).ToList(); var tagsToSave = tags.Except(savedUserTags.Select(x => x.Name).ToHashSet()).Select(x => new Tag { Name = x, UserId = currentUserId }).ToList(); await _context.Tags.AddRangeAsync(tagsToSave); await _context.SaveChangesAsync(); // Add newly added tags to the lookup savedUserTags.AddRange(tagsToSave); var existingBookmarkTags = _roContext.BookmarkTags.Include(x => x.Tag).Where(x => x.BookmarkId == id).ToList(); var existingBookmarkTagsLookup = existingBookmarkTags.ToDictionary(k => k.TagId, v => v.Tag.Name); var bookmarkTagsToRemove = existingBookmarkTagsLookup .Where(x => !tags.Contains(x.Value)) .Select(x => new BookmarkTag { BookmarkId = id, TagId = x.Key }); var savedUserTagsByName = savedUserTags.ToDictionary(k => k.Name, v => v.Id); var bookmarkTagsToAdd = tags.Except(existingBookmarkTagsLookup.Values) .Select(x => new BookmarkTag { BookmarkId = id, TagId = savedUserTagsByName[x] }); _context.BookmarkTags.RemoveRange(bookmarkTagsToRemove); await _context.BookmarkTags.AddRangeAsync(bookmarkTagsToAdd); if (await _context.SaveChangesAsync() >= 0) crudResults.ForEach(x => x.CrudResult = CrudResultLookup.UpdateSucceeded); return crudResults; } public async Task> Get(int id) { int.TryParse(_httpContextAccessor.HttpContext.User.Identity.GetUserId(), out int userId); var bookmark = await _roContext.Bookmarks.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId); if (bookmark == null) return new CrudResultDTO { CrudResult = CrudResultLookup.RetrieveFailed, Entry = null }; var bookmarkTags = _roContext.BookmarkTags .Include(x => x.Tag) .Where(x => x.BookmarkId == id) .Select(x => x.Tag) .ToList(); var bookmarkDTO = _mapper.Map(bookmark); bookmarkDTO.Tags = _mapper.Map>(bookmarkTags); return new CrudResultDTO { CrudResult = CrudResultLookup.RetrieveSuccessful, Entry = bookmarkDTO }; } public CrudResultDTO> GetBookmarkTags(int id) { int.TryParse(_httpContextAccessor.HttpContext.User.Identity.GetUserId(), out int userId); if (!_roContext.Bookmarks.Any(x => x.Id == id && x.UserId == userId)) return new CrudResultDTO> { Entry = null, CrudResult = CrudResultLookup.RetrieveFailed }; var bookmarkTags = _roContext.BookmarkTags .Include(x => x.Tag) .Where(x => x.BookmarkId == id) .Select(x => x.Tag) .ToList(); var bookmarkTagDTOs = _mapper.Map>(bookmarkTags); return new CrudResultDTO> { Entry = bookmarkTagDTOs, CrudResult = CrudResultLookup.RetrieveSuccessful }; } public async Task> DeleteBookmark(int id) { var crudResults = await DeleteBookmarks(new List { id }); return crudResults.FirstOrDefault(); } public async Task>> DeleteBookmarks(IEnumerable ids) { var crudResults = ids.Select(x => new CrudResultDTO { Entry = x, CrudResult = CrudResultLookup.DeleteFailed }).ToList(); var currentUserId = GetCurrentUserId(); if (!await _roContext.Users.UserExistsAsync(currentUserId)) return crudResults; var entriesToDelete = _context.Bookmarks.Where(x => x.UserId == currentUserId && ids.Contains(x.Id)).ToList(); var entryIdsToDelete = entriesToDelete.Select(x => x.Id); _context.Bookmarks.RemoveRange(entriesToDelete); if (await _context.SaveChangesAsync() <= 0) return crudResults; // Update crudResults that were found in the entriesToDelete to success foreach(var crudResult in crudResults) { if (entryIdsToDelete.Contains(crudResult.Entry)) crudResult.CrudResult = CrudResultLookup.DeleteSucceeded; } return crudResults; } private int GetCurrentUserId() { int.TryParse(_httpContextAccessor.HttpContext.User.Identity.GetUserId(), out int userId); return userId; } } }