diff --git a/Start/Server/Controllers/BookmarksController.cs b/Start/Server/Controllers/BookmarksController.cs index 4f25445..c9f4cd0 100644 --- a/Start/Server/Controllers/BookmarksController.cs +++ b/Start/Server/Controllers/BookmarksController.cs @@ -30,14 +30,20 @@ namespace Start.Server.Controllers { } */ - [HttpPost] - public Bookmark CreateBookmark(string title, string url, string? notes, - int bookmarkGroupId) { - string userId = this.User.FindFirst(ClaimTypes.NameIdentifier)!.Value; - - return bookmarkService.CreateBookmark(userId, title, url, notes, bookmarkGroupId); + [HttpGet] + public (BookmarkStatus, Bookmark?) GetBookmark(int bookmarkId) { + return this.bookmarkService.GetBookmark(this.GetUserId(), bookmarkId); } + [HttpPost] + public (BookmarkStatus, Bookmark?) CreateBookmark(string title, string url, string? notes, + int bookmarkGroupId) { + return this.bookmarkService.CreateBookmark(this.GetUserId(), title, url, notes, + bookmarkGroupId); + } + private string GetUserId() { + return this.User.FindFirst(ClaimTypes.NameIdentifier)!.Value; + } } } diff --git a/Start/Server/Data/Services/BookmarkContainerService.cs b/Start/Server/Data/Services/BookmarkContainerService.cs new file mode 100644 index 0000000..42aa78b --- /dev/null +++ b/Start/Server/Data/Services/BookmarkContainerService.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Start.Server.Data.Services.Interfaces; +using Start.Server.Models; + +namespace Start.Server.Data.Services { + public class BookmarkContainerService : IBookmarkContainerService { + private readonly ApplicationDbContext db; + + public BookmarkContainerService(ApplicationDbContext dbContext) { + this.db = dbContext; + } + + public (BookmarkStatus, BookmarkContainer?) GetBookmarkContainer(string userId, + int bookmarkContainerId, bool includeGroups = false, bool includeBookmarks = false) { + BookmarkContainer? bookmarkContainer = this.db.BookmarkContainers + .Where(bc => bc.BookmarkContainerId == bookmarkContainerId) + .If(includeGroups, q => q.Include(bc => bc.BookmarkGroups)) + .If(includeBookmarks, q => q + .Include(bc => bc.BookmarkGroups) + .ThenInclude(bg => bg.Bookmarks)) + .SingleOrDefault(); + + if (bookmarkContainer == null) + return (BookmarkStatus.BookmarkDoesNotExist, null); + + if (!BookmarkOwnershipTools + .IsBookmarkContainerOwner(this.db, userId, bookmarkContainerId)) + return (BookmarkStatus.OwnerDoesNotMatch, null); + + return (BookmarkStatus.OK, bookmarkContainer); + } + + public IList GetUserBookmarkContainers(string userId, + bool includeGroups = false, bool includeBookmarks = false) { + return this.db.BookmarkContainers + .Where(bc => bc.ApplicationUserId == userId) + .If(includeGroups, q => q.Include(bc => bc.BookmarkGroups)) + .If(includeBookmarks, q => q + .Include(bc => bc.BookmarkGroups) + .ThenInclude(bg => bg.Bookmarks)) + .ToList(); + } + + public (BookmarkStatus, BookmarkContainer?) CreateBookmarkContainer(string userId, + string title) { + BookmarkContainer newContainer = new(userId, title); + this.db.BookmarkContainers.Add(newContainer); + return (BookmarkStatus.OK, newContainer); + } + + public (BookmarkStatus, BookmarkContainer?) UpdateBookmarkContainer(string userId, + BookmarkContainer bookmarkContainer) { + BookmarkContainer? exitingBookmarkContainer = this.db.BookmarkContainers + .SingleOrDefault(bc => bc.BookmarkContainerId + == bookmarkContainer.BookmarkContainerId); + + if (exitingBookmarkContainer == null) + return (BookmarkStatus.BookmarkDoesNotExist, null); + + if (!BookmarkOwnershipTools + .IsBookmarkContainerOwner(this.db, userId, bookmarkContainer.BookmarkContainerId)) + return (BookmarkStatus.OwnerDoesNotMatch, null); + + this.db.Entry(bookmarkContainer).State = EntityState.Modified; + this.db.SaveChanges(); + + return (BookmarkStatus.OK, bookmarkContainer); + } + + public BookmarkStatus DeleteBookmarkContainer(string userId, int bookmarkContainerId) { + BookmarkContainer? bookmarkContainer = this.db.BookmarkContainers + .Where(bc => bc.BookmarkContainerId == bookmarkContainerId) + .SingleOrDefault(); + + if (bookmarkContainer == null) + return (BookmarkStatus.BookmarkDoesNotExist); + + if (!BookmarkOwnershipTools.IsBookmarkContainerOwner(this.db, userId, bookmarkContainerId)) + return BookmarkStatus.OwnerDoesNotMatch; + + this.db.BookmarkContainers.Remove(bookmarkContainer); + this.db.SaveChanges(); + + return BookmarkStatus.OK; + } + } +} diff --git a/Start/Server/Data/Services/BookmarkGroupService.cs b/Start/Server/Data/Services/BookmarkGroupService.cs new file mode 100644 index 0000000..0929e10 --- /dev/null +++ b/Start/Server/Data/Services/BookmarkGroupService.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Start.Server.Data.Services.Interfaces; +using Start.Server.Models; + +namespace Start.Server.Data.Services { + public class BookmarkGroupService : IBookmarkGroupService { + private readonly ApplicationDbContext db; + + public BookmarkGroupService(ApplicationDbContext dbContext) { + this.db = dbContext; + } + + public (BookmarkStatus, BookmarkGroup?) GetBookmarkGroup(string userId, + int bookmarkGroupId, bool includeBookmarks = false) { + BookmarkGroup? group = db.BookmarkGroups + .Where(bg => bg.BookmarkGroupId == bookmarkGroupId) + .If(includeBookmarks, q => q.Include(bg => bg.Bookmarks)) + .SingleOrDefault(); + + if (group == null) + return (BookmarkStatus.BookmarkDoesNotExist, null); + + if (!BookmarkOwnershipTools.IsBookmarkGroupOwner(db, userId, bookmarkGroupId)) + return (BookmarkStatus.OwnerDoesNotMatch, null); + + return (BookmarkStatus.OK, group); + } + + public IList GetUserBookmarkGroups(string userId, + bool includeBookmarkGroups = false) { + return this.db.BookmarkGroups + .Where(bg => bg.BookmarkContainer!.ApplicationUserId == userId) + .If(includeBookmarkGroups, q => q.Include(bg => bg.Bookmarks)) + .ToList(); + } + + public (BookmarkStatus, BookmarkGroup?) CreateBookmarkGroup(string userId, string title, + string color, int bookmarkContainerId) { + if (!BookmarkOwnershipTools + .IsBookmarkContainerOwner(this.db, userId, bookmarkContainerId)) + return (BookmarkStatus.OwnerDoesNotMatch, null); + + BookmarkGroup newBookmarkGroup = new(title, color, bookmarkContainerId); + this.db.BookmarkGroups.Add(newBookmarkGroup); + this.db.SaveChanges(); + + return (BookmarkStatus.OK, newBookmarkGroup); + } + + public (BookmarkStatus, BookmarkGroup?) UpdateBookmarkGroup(string userId, + BookmarkGroup bookmarkGroup) { + BookmarkGroup? existingGroup = this.db.BookmarkGroups + .SingleOrDefault(bg => bg.BookmarkGroupId == bookmarkGroup.BookmarkGroupId); + + if (existingGroup == null) + return (BookmarkStatus.BookmarkDoesNotExist, null); + + if (!BookmarkOwnershipTools + .IsBookmarkGroupOwner(this.db, userId, bookmarkGroup.BookmarkGroupId)) + return (BookmarkStatus.OwnerDoesNotMatch, null); + + this.db.Entry(bookmarkGroup).State = EntityState.Modified; + this.db.SaveChanges(); + + return (BookmarkStatus.OK, bookmarkGroup); + } + + public BookmarkStatus DeleteBookmarkGroup(string userId, int bookmarkGroupId) { + BookmarkGroup? bookmarkGroup = this.db.BookmarkGroups + .SingleOrDefault(bg => bg.BookmarkGroupId == bookmarkGroupId); + + if (bookmarkGroup == null) + return BookmarkStatus.BookmarkDoesNotExist; + + if (!BookmarkOwnershipTools.IsBookmarkGroupOwner(this.db, userId, bookmarkGroupId)) + return BookmarkStatus.OwnerDoesNotMatch; + + this.db.BookmarkGroups.Remove(bookmarkGroup); + this.db.SaveChanges(); + + return BookmarkStatus.OK; + } + } +} diff --git a/Start/Server/Data/Services/BookmarkOwnershipTools.cs b/Start/Server/Data/Services/BookmarkOwnershipTools.cs new file mode 100644 index 0000000..a9d14c5 --- /dev/null +++ b/Start/Server/Data/Services/BookmarkOwnershipTools.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; + +namespace Start.Server.Data.Services { + public static class BookmarkOwnershipTools { + public static bool IsBookmarkOwner(ApplicationDbContext db, string userId, int bookmarkId) { + string? bookmarkOwnerId = db.Bookmarks + .Where(b => b.BookmarkId == bookmarkId) + .Select(b => b.BookmarkGroup!.BookmarkContainer!.ApplicationUserId) + .SingleOrDefault(); + + return userId == bookmarkOwnerId; + } + + public static bool IsBookmarkGroupOwner(ApplicationDbContext db, string userId, + int bookmarkGroupId) { + string? groupOwnerId = db.BookmarkGroups + .Where(bg => bg.BookmarkGroupId == bookmarkGroupId) + .Select(bg => bg.BookmarkContainer!.ApplicationUserId) + .SingleOrDefault(); + + return userId == groupOwnerId; + } + + public static bool IsBookmarkContainerOwner(ApplicationDbContext db, string userId, + int bookmarkContainerId) { + string? containerOwnerId = db.BookmarkContainers + .Where(bc => bc.BookmarkContainerId == bookmarkContainerId) + .Select(bc => bc.ApplicationUserId) + .SingleOrDefault(); + + return userId == containerOwnerId; + } + } +} diff --git a/Start/Server/Data/Services/BookmarkService.cs b/Start/Server/Data/Services/BookmarkService.cs index f63e0a3..c7edc67 100644 --- a/Start/Server/Data/Services/BookmarkService.cs +++ b/Start/Server/Data/Services/BookmarkService.cs @@ -15,7 +15,7 @@ namespace Start.Server.Data.Services { } public (BookmarkStatus, Bookmark?) GetBookmark(string userId, int bookmarkId) { - if (!IsBookmarkOwner(userId, bookmarkId)) + if (!BookmarkOwnershipTools.IsBookmarkOwner(this.db, userId, bookmarkId)) return (BookmarkStatus.OwnerDoesNotMatch, null); Bookmark? bookmark = this.db.Bookmarks @@ -33,18 +33,17 @@ namespace Start.Server.Data.Services { .ToList(); } - public Bookmark CreateBookmark(string userId, string title, string url, string? notes, + public (BookmarkStatus, Bookmark?) CreateBookmark(string userId, string title, string url, string? notes, int bookmarkGroupId) { - if (!this.IsBookmarkGroupOwner(userId, bookmarkGroupId)) - throw new SecurityException( - "The provided user ID doesn't match the bookmark group owner ID"); + if (!BookmarkOwnershipTools.IsBookmarkGroupOwner(this.db, userId, bookmarkGroupId)) + return (BookmarkStatus.OwnerDoesNotMatch, null); Bookmark newBookmark = new(title, url, bookmarkGroupId); db.Bookmarks.Add(newBookmark); db.SaveChanges(); - return newBookmark; + return (BookmarkStatus.OK, newBookmark); } public (BookmarkStatus, Bookmark?) UpdateBookmark(string userId, Bookmark bookmark) { @@ -54,10 +53,11 @@ namespace Start.Server.Data.Services { if (existingBookmark == null) return (BookmarkStatus.BookmarkDoesNotExist, null); - if (!IsBookmarkOwner(userId, bookmark.BookmarkId)) + if (!BookmarkOwnershipTools.IsBookmarkOwner(this.db, userId, bookmark.BookmarkId)) return (BookmarkStatus.OwnerDoesNotMatch, null); - if (!IsBookmarkGroupOwner(userId, bookmark.BookmarkGroupId)) + if (!BookmarkOwnershipTools + .IsBookmarkGroupOwner(this.db, userId, bookmark.BookmarkGroupId)) return (BookmarkStatus.OwnerDoesNotMatch, null); db.Entry(bookmark).State = EntityState.Modified; @@ -73,7 +73,7 @@ namespace Start.Server.Data.Services { if (bookmark == null) return BookmarkStatus.BookmarkDoesNotExist; - if (!IsBookmarkOwner(userId, bookmarkId)) + if (!BookmarkOwnershipTools.IsBookmarkOwner(this.db, userId, bookmarkId)) return BookmarkStatus.OwnerDoesNotMatch; db.Bookmarks.Remove(bookmark); @@ -81,23 +81,5 @@ namespace Start.Server.Data.Services { return BookmarkStatus.OK; } - - private bool IsBookmarkOwner(string userId, int bookmarkId) { - string? bookmarkOwnerId = this.db.Bookmarks - .Where(b => b.BookmarkId == bookmarkId) - .Select(b => b.BookmarkGroup!.BookmarkContainer!.ApplicationUserId) - .SingleOrDefault(); - - return userId == bookmarkOwnerId; - } - - private bool IsBookmarkGroupOwner(string userId, int bookmarkGroupId) { - string? groupOwnerId = this.db.BookmarkGroups - .Where(bg => bg.BookmarkGroupId == bookmarkGroupId) - .Select(bg => bg.BookmarkContainer!.ApplicationUserId) - .SingleOrDefault(); - - return userId == groupOwnerId; - } } } diff --git a/Start/Server/Data/Services/Interfaces/BookmarkStatus.cs b/Start/Server/Data/Services/Interfaces/BookmarkStatus.cs new file mode 100644 index 0000000..35e373f --- /dev/null +++ b/Start/Server/Data/Services/Interfaces/BookmarkStatus.cs @@ -0,0 +1,9 @@ +using System; +namespace Start.Server.Data.Services.Interfaces { + public enum BookmarkStatus { + OK = 1, + BookmarkDoesNotExist = 2, + OwnerDoesNotMatch = 3, + UserDoesNotExist = 4 + } +} diff --git a/Start/Server/Data/Services/Interfaces/IBookmarkContainerService.cs b/Start/Server/Data/Services/Interfaces/IBookmarkContainerService.cs new file mode 100644 index 0000000..ef1d3d0 --- /dev/null +++ b/Start/Server/Data/Services/Interfaces/IBookmarkContainerService.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using Start.Server.Models; + +namespace Start.Server.Data.Services.Interfaces { + public interface IBookmarkContainerService { + public (BookmarkStatus, BookmarkContainer?) GetBookmarkContainer(string userId, + int bookmarkContainerId, bool includeGroups = false, bool includeBookmarks = false); + public IList GetUserBookmarkContainers(string userId, + bool includeGroups = false, bool includeBookmarks = false); + + public (BookmarkStatus, BookmarkContainer?) CreateBookmarkContainer(string userId, + string title); + public (BookmarkStatus, BookmarkContainer?) UpdateBookmarkContainer(string userId, + BookmarkContainer bookmarkContainer); + public BookmarkStatus DeleteBookmarkContainer(string userId, int bookmarkContainerId); + } +} diff --git a/Start/Server/Data/Services/Interfaces/IBookmarkGroupService.cs b/Start/Server/Data/Services/Interfaces/IBookmarkGroupService.cs new file mode 100644 index 0000000..e5cfc66 --- /dev/null +++ b/Start/Server/Data/Services/Interfaces/IBookmarkGroupService.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using Start.Server.Models; + +namespace Start.Server.Data.Services.Interfaces { + public interface IBookmarkGroupService { + public (BookmarkStatus, BookmarkGroup?) GetBookmarkGroup(string userId, + int bookmarkGroupId, bool includeBookmarks = false); + public IList GetUserBookmarkGroups(string userId, + bool includeBookmarks = false); + + public (BookmarkStatus, BookmarkGroup?) CreateBookmarkGroup(string userId, string title, + string color, int bookmarkContainerId); + public (BookmarkStatus, BookmarkGroup?) UpdateBookmarkGroup(string userId, + BookmarkGroup bookmarkGroup); + public BookmarkStatus DeleteBookmarkGroup(string userId, int bookmarkGroupId); + } +} diff --git a/Start/Server/Data/Services/Interfaces/IBookmarkService.cs b/Start/Server/Data/Services/Interfaces/IBookmarkService.cs index 426e778..a020e01 100644 --- a/Start/Server/Data/Services/Interfaces/IBookmarkService.cs +++ b/Start/Server/Data/Services/Interfaces/IBookmarkService.cs @@ -7,16 +7,9 @@ namespace Start.Server.Data.Services.Interfaces { public (BookmarkStatus, Bookmark?) GetBookmark(string userId, int bookmarkId); public IList GetUserBookmarks(string userId); - public Bookmark CreateBookmark(string userId, string title, string url, string? notes, - int bookmarkGroupId); + public (BookmarkStatus, Bookmark?) CreateBookmark(string userId, string title, string url, + string? notes, int bookmarkGroupId); public (BookmarkStatus, Bookmark?) UpdateBookmark(string userId, Bookmark bookmark); public BookmarkStatus DeleteBookmark(string userId, int bookmarkId); } - - public enum BookmarkStatus { - OK = 1, - BookmarkDoesNotExist = 2, - OwnerDoesNotMatch = 3, - UserDoesNotExist = 4 - } } diff --git a/Start/Server/LinqExtensions.cs b/Start/Server/LinqExtensions.cs new file mode 100644 index 0000000..cc46a51 --- /dev/null +++ b/Start/Server/LinqExtensions.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using System.Linq.Expressions; + +namespace Start.Server { + /// Extension methods for LINQ queries + public static class LinqExtensions { + /// + /// If is true, then returns query.Where(expression). + /// Otherwise returns the unchanged. + /// + /// The query to potentially apply the expression to + /// + /// Determines whether or not the expression should be applied + /// + /// A function to test each element + public static IQueryable IfWhere(this IQueryable query, bool condition, + Expression> expression) { + if (condition) + return query.Where(expression); + + return query; + } + + /// + /// If the is true, apply the to + /// the query, otherwise return the query unchanged. + /// + /// The 's type + /// The query to potentially transform + /// + /// If true, apply the to the + /// + /// + /// A function to apply to the if the + /// is true + /// + public static IQueryable If(this IQueryable query, bool condition, + Func, IQueryable> transform) { + if (condition) + return transform(query); + + return query; + } + } +} diff --git a/Start/Server/Models/BookmarkGroup.cs b/Start/Server/Models/BookmarkGroup.cs index 182a5a4..49bb4e9 100644 --- a/Start/Server/Models/BookmarkGroup.cs +++ b/Start/Server/Models/BookmarkGroup.cs @@ -24,12 +24,14 @@ namespace Start.Server.Models { /// The bookmarks in this group public List? Bookmarks { get; set; } - public BookmarkGroup(string title, string color) { + public BookmarkGroup(string title, string color, int bookmarkContainerId) { this.Title = title; this.Color = color; + this.BookmarkContainerId = bookmarkContainerId; } - public BookmarkGroup(int bookmarkGroupId, string title, string color) : this(title, color) { + public BookmarkGroup(int bookmarkGroupId, string title, string color, + int bookmarkContainerId) : this(title, color, bookmarkContainerId) { this.BookmarkGroupId = bookmarkGroupId; } } diff --git a/Start/Server/Startup.cs b/Start/Server/Startup.cs index a5103cd..48eba48 100644 --- a/Start/Server/Startup.cs +++ b/Start/Server/Startup.cs @@ -41,6 +41,8 @@ namespace Start.Server { services.AddRazorPages(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } // This method gets called by the runtime. Use this method to configure the HTTP request @@ -49,7 +51,6 @@ namespace Start.Server { ApplicationDbContext context) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - app.UseMigrationsEndPoint(); app.UseWebAssemblyDebugging(); } else {