diff --git a/YABA.API/Controllers/BookmarksController.cs b/YABA.API/Controllers/BookmarksController.cs new file mode 100644 index 0000000..394db39 --- /dev/null +++ b/YABA.API/Controllers/BookmarksController.cs @@ -0,0 +1,103 @@ +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Net; +using YABA.API.ViewModels; +using YABA.Common.DTOs.Bookmarks; +using YABA.Common.DTOs.Tags; +using YABA.Service.Interfaces; + +namespace YABA.API.Controllers +{ + [ApiVersion("1")] + [Authorize, Route("api/v{version:apiVersion}/[controller]")] + public class BookmarksController : ControllerBase + { + private readonly IBookmarkService _bookmarkService; + + public BookmarksController(IBookmarkService bookmarkService) + { + _bookmarkService = bookmarkService; + } + + [HttpPost] + [ProducesResponseType(typeof(GenericResponse), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public async Task Create(CreateBookmarkRequestDTO request) + { + var result = await _bookmarkService.CreateBookmark(request); + + if(!result.IsSuccessful) return BadRequest(); + + return Ok(new GenericResponse(result)); + } + + [HttpPost("{id}/Tags")] + [ProducesResponseType(typeof(IEnumerable>),(int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task UpdateBookmarkTags(int id, IEnumerable tags) + { + var result = await _bookmarkService.UpdateBookmarkTags(id, tags); + + if (result.All(x => !x.IsSuccessful)) return NotFound(); + + return Ok(result.Select(x => new GenericResponse(x))); + } + + [HttpGet] + [ProducesResponseType(typeof(GenericResponse>), (int)HttpStatusCode.OK)] + public IActionResult GetAll() + { + var result = _bookmarkService.GetAll(); + return Ok(new GenericResponse>(result)); + } + + [HttpGet("{id}")] + [ProducesResponseType(typeof(GenericResponse), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task Get(int id) + { + var result = await _bookmarkService.Get(id); + + if (!result.IsSuccessful) return NotFound(); + + return Ok(new GenericResponse(result)); + } + + [HttpGet("{id}/Tags")] + [ProducesResponseType(typeof(GenericResponse>), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public IActionResult GetBookmarkTags(int id) + { + var result = _bookmarkService.GetBookmarkTags(id); + + if (!result.IsSuccessful) return NotFound(); + + return Ok(new GenericResponse>(result)); + } + + [HttpDelete("{id}")] + [ProducesResponseType(typeof(GenericResponse), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task Delete(int id) + { + var result = await _bookmarkService.DeleteBookmark(id); + + if (!result.IsSuccessful) return NotFound(); + + return Ok(new GenericResponse(result)); + } + + [HttpDelete()] + [ProducesResponseType(typeof(IEnumerable>), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + public async Task DeleteBookmarks(IEnumerable ids) + { + var result = await _bookmarkService.DeleteBookmarks(ids); + + if(result.All(x => !x.IsSuccessful)) return NotFound(); + + return Ok(result.Select((x) => new GenericResponse(x))); + } + } +} \ No newline at end of file diff --git a/YABA.API/Extensions/ControllerExtensions.cs b/YABA.API/Extensions/ControllerExtensions.cs index 5c0622c..3e07c23 100644 --- a/YABA.API/Extensions/ControllerExtensions.cs +++ b/YABA.API/Extensions/ControllerExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using YABA.Common.Extensions; using YABA.Common.Lookups; namespace YABA.API.Extensions diff --git a/YABA.API/Program.cs b/YABA.API/Program.cs index 83d6b16..bb8e69c 100644 --- a/YABA.API/Program.cs +++ b/YABA.API/Program.cs @@ -43,7 +43,8 @@ builder.Services.AddApiVersioning(setup => setup.ApiVersionReader = new UrlSegmentApiVersionReader(); }); -// Add services to the container. +// Add services to the container +builder.Services.AddHttpContextAccessor(); builder.Services.AddServiceProjectDependencyInjectionConfiguration(configuration); builder.Services.AddDataProjectDependencyInjectionConfiguration(configuration); builder.Services.AddControllers().AddNewtonsoftJson(); diff --git a/YABA.API/Settings/AutoMapperProfile.cs b/YABA.API/Settings/AutoMapperProfile.cs index 53cc77a..9b2d941 100644 --- a/YABA.API/Settings/AutoMapperProfile.cs +++ b/YABA.API/Settings/AutoMapperProfile.cs @@ -1,6 +1,7 @@ using AutoMapper; using YABA.API.ViewModels; using YABA.Common.DTOs; +using YABA.Common.DTOs.Tags; namespace YABA.API.Settings { diff --git a/YABA.API/ViewModels/Response.cs b/YABA.API/ViewModels/Response.cs new file mode 100644 index 0000000..c649b83 --- /dev/null +++ b/YABA.API/ViewModels/Response.cs @@ -0,0 +1,22 @@ +using YABA.Common.DTOs; +using YABA.Common.Extensions; + +namespace YABA.API.ViewModels +{ + public class GenericResponse + { + public T Entry { get; set; } + public int StatusId { get; set; } + public string StatusName { get; set; } + public string StatusMessage { get; set; } + + public GenericResponse(CrudResultDTO value) + { + // TODO: Find a way to bring this to AutoMapper + Entry = value.Entry; + StatusId = (int)value.CrudResult; + StatusName = value.CrudResult.ToString(); + StatusMessage = value.CrudResult.GetDisplayName(); + } + } +} diff --git a/YABA.API/ViewModels/UserResponse.cs b/YABA.API/ViewModels/UserResponse.cs index 0abae39..6225624 100644 --- a/YABA.API/ViewModels/UserResponse.cs +++ b/YABA.API/ViewModels/UserResponse.cs @@ -1,5 +1,4 @@ -using YABA.Service.DTO; - + namespace YABA.API.ViewModels { public class UserResponse @@ -8,13 +7,5 @@ namespace YABA.API.ViewModels public bool IsDeleted { get; set; } public DateTimeOffset CreatedOn { get; set; } public DateTimeOffset LastModified { get; set; } - - public UserResponse(UserDTO value) - { - Id = value.Id; - IsDeleted = value.IsDeleted; - CreatedOn = value.CreatedOn; - LastModified = value.LastModified; - } } } diff --git a/YABA.Common/DTOs/Bookmarks/BookmarkDTO.cs b/YABA.Common/DTOs/Bookmarks/BookmarkDTO.cs new file mode 100644 index 0000000..ab55fc5 --- /dev/null +++ b/YABA.Common/DTOs/Bookmarks/BookmarkDTO.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using YABA.Common.DTOs.Tags; +using YABA.Common.Interfaces; + +namespace YABA.Common.DTOs.Bookmarks +{ + public class BookmarkDTO : IBookmark + { + public int Id { get; set; } + public DateTimeOffset CreatedOn { get; set; } + public DateTimeOffset LastModified { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public string Note { get; set; } + public bool IsHidden { get; set; } + public string Url { get; set; } + public IList Tags { get; set; } = new List(); + + } +} diff --git a/YABA.Common/DTOs/Bookmarks/CreateBookmarkRequestDTO.cs b/YABA.Common/DTOs/Bookmarks/CreateBookmarkRequestDTO.cs new file mode 100644 index 0000000..148a378 --- /dev/null +++ b/YABA.Common/DTOs/Bookmarks/CreateBookmarkRequestDTO.cs @@ -0,0 +1,13 @@ +using YABA.Common.Interfaces; + +namespace YABA.Common.DTOs.Bookmarks +{ + public class CreateBookmarkRequestDTO : IBookmark + { + public string Title { get; set; } + public string Description { get; set; } + public string Note { get; set; } + public bool IsHidden { get; set; } + public string Url { get; set; } + } +} diff --git a/YABA.Common/DTOs/Bookmarks/UpdateBookmarkRequestDTO.cs b/YABA.Common/DTOs/Bookmarks/UpdateBookmarkRequestDTO.cs new file mode 100644 index 0000000..fabdcdc --- /dev/null +++ b/YABA.Common/DTOs/Bookmarks/UpdateBookmarkRequestDTO.cs @@ -0,0 +1,13 @@ +using YABA.Common.Interfaces; + +namespace YABA.Common.DTOs.Bookmarks +{ + public class UpdateBookmarkRequestDTO : IBookmark + { + public string Title { get; set; } + public string Description { get; set; } + public string Note { get; set; } + public bool IsHidden { get; set; } + public string Url { get; set; } + } +} diff --git a/YABA.Common/DTOs/CrudResultDTO.cs b/YABA.Common/DTOs/CrudResultDTO.cs new file mode 100644 index 0000000..176d56a --- /dev/null +++ b/YABA.Common/DTOs/CrudResultDTO.cs @@ -0,0 +1,12 @@ +using YABA.Common.Extensions; +using YABA.Common.Lookups; + +namespace YABA.Common.DTOs +{ + public class CrudResultDTO + { + public CrudResultLookup CrudResult { get; set; } + public T Entry { get; set; } + public bool IsSuccessful => CrudResult.IsCrudResultSuccessful(); + } +} diff --git a/YABA.Common/DTOs/Tags/TagDTO.cs b/YABA.Common/DTOs/Tags/TagDTO.cs new file mode 100644 index 0000000..41b7d0e --- /dev/null +++ b/YABA.Common/DTOs/Tags/TagDTO.cs @@ -0,0 +1,10 @@ +namespace YABA.Common.DTOs.Tags +{ + public class TagDTO + { + public int Id { get; set; } + public bool IsDeleted { get; set; } + public string Name { get; set; } + public bool IsHidden { get; set; } + } +} diff --git a/YABA.Common/DTOs/Tags/TagSummaryDTO.cs b/YABA.Common/DTOs/Tags/TagSummaryDTO.cs new file mode 100644 index 0000000..ce38a9d --- /dev/null +++ b/YABA.Common/DTOs/Tags/TagSummaryDTO.cs @@ -0,0 +1,9 @@ + +namespace YABA.Common.DTOs.Tags +{ + public class TagSummaryDTO + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/YABA.Common/Extensions/DictionaryExtensions.cs b/YABA.Common/Extensions/DictionaryExtensions.cs new file mode 100644 index 0000000..49810dc --- /dev/null +++ b/YABA.Common/Extensions/DictionaryExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace YABA.Common.Extensions +{ + public static class DictionaryExtensions + { + public static void AddRange(this Dictionary source, Dictionary collection) + { + if (collection == null) + { + throw new ArgumentNullException("Collection is null"); + } + + foreach (var item in collection) + { + if (!source.ContainsKey(item.Key)) + { + source.Add(item.Key, item.Value); + } + } + } + } +} diff --git a/YABA.Common/Extensions/EnumExtensions.cs b/YABA.Common/Extensions/EnumExtensions.cs index b942726..f87847e 100644 --- a/YABA.Common/Extensions/EnumExtensions.cs +++ b/YABA.Common/Extensions/EnumExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using YABA.Common.Attributes; @@ -8,6 +9,13 @@ namespace YABA.Common.Extensions { public static class EnumExtensions { + private static readonly IEnumerable SuccessfulCrudStatuses = new List() { + CrudResultLookup.CreateSucceeded, + CrudResultLookup.UpdateSucceeded, + CrudResultLookup.DeleteSucceeded, + CrudResultLookup.RetrieveSuccessful + }; + public static TAttribute GetAttribute(this Enum value) where TAttribute : Attribute { var enumType = value.GetType(); @@ -25,5 +33,8 @@ namespace YABA.Common.Extensions return claimLookup.GetAttribute().Name; } + public static bool IsCrudResultSuccessful(this CrudResultLookup importStatusLookup) => SuccessfulCrudStatuses.Contains(importStatusLookup); + + public static bool IsCrudResultFailure(this CrudResultLookup importStatusLookup) => !SuccessfulCrudStatuses.Contains(importStatusLookup); } } diff --git a/YABA.API/Extensions/UserIdentityExtensions.cs b/YABA.Common/Extensions/UserIdentityExtensions.cs similarity index 78% rename from YABA.API/Extensions/UserIdentityExtensions.cs rename to YABA.Common/Extensions/UserIdentityExtensions.cs index 89cec1c..67132db 100644 --- a/YABA.API/Extensions/UserIdentityExtensions.cs +++ b/YABA.Common/Extensions/UserIdentityExtensions.cs @@ -1,12 +1,12 @@ using System.Security.Claims; using System.Security.Principal; -using YABA.Common.Extensions; using YABA.Common.Lookups; -namespace YABA.API.Extensions +namespace YABA.Common.Extensions { public static class UserIdentityExtensions { + public static string GetUserId(this IIdentity identity) => GetCustomClaim(identity, ClaimsLookup.UserId); public static string GetAuthProviderId(this IIdentity identity) => GetCustomClaim(identity, ClaimsLookup.AuthProviderId); public static string GetCustomClaim(this IIdentity identity, ClaimsLookup claim) diff --git a/YABA.Common/Interfaces/IBookmark.cs b/YABA.Common/Interfaces/IBookmark.cs new file mode 100644 index 0000000..7ae2aed --- /dev/null +++ b/YABA.Common/Interfaces/IBookmark.cs @@ -0,0 +1,11 @@ + +namespace YABA.Common.Interfaces +{ + public interface IBookmark + { + public string Title { get; set; } + public string Description { get; set; } + public string Note { get; set; } + public bool IsHidden { get; set; } + } +} diff --git a/YABA.Common/Lookups/CrudResultLookup.cs b/YABA.Common/Lookups/CrudResultLookup.cs new file mode 100644 index 0000000..1b1df43 --- /dev/null +++ b/YABA.Common/Lookups/CrudResultLookup.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; + +namespace YABA.Common.Lookups +{ + public enum CrudResultLookup + { + [Display(Name = "Insert failed")] + CreateFailed = 1, + + [Display(Name = "Insert succeeded")] + CreateSucceeded = 2, + + [Display(Name = "Insert failed. Entry already exists")] + CreateFailedEntryExists = 3, + + [Display(Name = "Update failed")] + UpdateFailed = 4, + + [Display(Name = "Update succeeded")] + UpdateSucceeded = 5, + + [Display(Name = "Delete failed")] + DeleteFailed = 6, + + [Display(Name = "Delete succeeded")] + DeleteSucceeded = 7, + + [Display(Name = "Retrieve failed")] + RetrieveFailed = 8, + + [Display(Name = "Retrieve successful")] + RetrieveSuccessful = 9 + } +} diff --git a/YABA.Data/Context/YABABaseContext.cs b/YABA.Data/Context/YABABaseContext.cs index 6593d3a..33237ca 100644 --- a/YABA.Data/Context/YABABaseContext.cs +++ b/YABA.Data/Context/YABABaseContext.cs @@ -32,12 +32,15 @@ namespace YABA.Data.Context }); modelBuilder.Entity() - .HasIndex(x => new { x.BookmarkId, x.TagId }) - .IsUnique(); + .HasKey(x => new { x.BookmarkId, x.TagId }); modelBuilder.Entity() .HasIndex(x => x.Auth0Id) .IsUnique(); + + modelBuilder.Entity() + .HasIndex(x => x.Name) + .IsUnique(); } public DbSet Bookmarks { get; set; } diff --git a/YABA.Data/Extensions/UsersDbSetExtensions.cs b/YABA.Data/Extensions/UsersDbSetExtensions.cs new file mode 100644 index 0000000..d9c0fad --- /dev/null +++ b/YABA.Data/Extensions/UsersDbSetExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using YABA.Models; + +namespace YABA.Data.Extensions +{ + public static class UsersDbSetExtensions + { + public static async Task UserExistsAsync(this DbSet userDbSet, int userId) + { + return await userDbSet.AnyAsync(x => x.Id == userId); + } + + public static bool UserExists(this DbSet userDbSet, int userId) + { + return userDbSet.Any(x => x.Id == userId); + } + } +} diff --git a/YABA.Data/Migrations/20230126050046_AddedUrlToBookmark.Designer.cs b/YABA.Data/Migrations/20230126050046_AddedUrlToBookmark.Designer.cs new file mode 100644 index 0000000..c9c9188 --- /dev/null +++ b/YABA.Data/Migrations/20230126050046_AddedUrlToBookmark.Designer.cs @@ -0,0 +1,226 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using YABA.Data.Context; + +namespace YABA.Data.Migrations +{ + [DbContext(typeof(YABABaseContext))] + [Migration("20230126050046_AddedUrlToBookmark")] + partial class AddedUrlToBookmark + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.17") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("YABA.Models.Bookmark", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("IsHidden") + .HasColumnType("boolean") + .HasColumnName("is_hidden"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_modified"); + + b.Property("Note") + .IsRequired() + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text") + .HasColumnName("url"); + + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_bookmarks"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_bookmarks_user_id"); + + b.ToTable("bookmarks"); + }); + + modelBuilder.Entity("YABA.Models.BookmarkTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("BookmarkId") + .HasColumnType("integer") + .HasColumnName("bookmark_id"); + + b.Property("TagId") + .HasColumnType("integer") + .HasColumnName("tag_id"); + + b.HasKey("Id") + .HasName("pk_bookmark_tags"); + + b.HasIndex("TagId") + .HasDatabaseName("ix_bookmark_tags_tag_id"); + + b.HasIndex("BookmarkId", "TagId") + .IsUnique() + .HasDatabaseName("ix_bookmark_tags_bookmark_id_tag_id"); + + b.ToTable("bookmark_tags"); + }); + + modelBuilder.Entity("YABA.Models.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("IsHidden") + .HasColumnType("boolean") + .HasColumnName("is_hidden"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tags"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tags_user_id"); + + b.ToTable("tags"); + }); + + modelBuilder.Entity("YABA.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Auth0Id") + .IsRequired() + .HasColumnType("text") + .HasColumnName("auth0id"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_modified"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("Auth0Id") + .IsUnique() + .HasDatabaseName("ix_users_auth0id"); + + b.ToTable("users"); + }); + + modelBuilder.Entity("YABA.Models.Bookmark", b => + { + b.HasOne("YABA.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_bookmarks_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("YABA.Models.BookmarkTag", b => + { + b.HasOne("YABA.Models.Bookmark", "Bookmark") + .WithMany() + .HasForeignKey("BookmarkId") + .HasConstraintName("fk_bookmark_tags_bookmarks_bookmark_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("YABA.Models.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .HasConstraintName("fk_bookmark_tags_tags_tag_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Bookmark"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("YABA.Models.Tag", b => + { + b.HasOne("YABA.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_tags_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/YABA.Data/Migrations/20230126050046_AddedUrlToBookmark.cs b/YABA.Data/Migrations/20230126050046_AddedUrlToBookmark.cs new file mode 100644 index 0000000..8110f67 --- /dev/null +++ b/YABA.Data/Migrations/20230126050046_AddedUrlToBookmark.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace YABA.Data.Migrations +{ + public partial class AddedUrlToBookmark : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "url", + table: "bookmarks", + type: "text", + nullable: false, + defaultValue: ""); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "url", + table: "bookmarks"); + } + } +} diff --git a/YABA.Data/Migrations/20230128064541_ModifiedBookmarkTagsPK_RemovedSoftDeleteFromTagsAndBookmarks.Designer.cs b/YABA.Data/Migrations/20230128064541_ModifiedBookmarkTagsPK_RemovedSoftDeleteFromTagsAndBookmarks.Designer.cs new file mode 100644 index 0000000..e9926e7 --- /dev/null +++ b/YABA.Data/Migrations/20230128064541_ModifiedBookmarkTagsPK_RemovedSoftDeleteFromTagsAndBookmarks.Designer.cs @@ -0,0 +1,212 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using YABA.Data.Context; + +namespace YABA.Data.Migrations +{ + [DbContext(typeof(YABABaseContext))] + [Migration("20230128064541_ModifiedBookmarkTagsPK_RemovedSoftDeleteFromTagsAndBookmarks")] + partial class ModifiedBookmarkTagsPK_RemovedSoftDeleteFromTagsAndBookmarks + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.17") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("YABA.Models.Bookmark", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsHidden") + .HasColumnType("boolean") + .HasColumnName("is_hidden"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_modified"); + + b.Property("Note") + .IsRequired() + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text") + .HasColumnName("url"); + + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_bookmarks"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_bookmarks_user_id"); + + b.ToTable("bookmarks"); + }); + + modelBuilder.Entity("YABA.Models.BookmarkTag", b => + { + b.Property("BookmarkId") + .HasColumnType("integer") + .HasColumnName("bookmark_id"); + + b.Property("TagId") + .HasColumnType("integer") + .HasColumnName("tag_id"); + + b.HasKey("BookmarkId", "TagId") + .HasName("pk_bookmark_tags"); + + b.HasIndex("TagId") + .HasDatabaseName("ix_bookmark_tags_tag_id"); + + b.ToTable("bookmark_tags"); + }); + + modelBuilder.Entity("YABA.Models.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("IsHidden") + .HasColumnType("boolean") + .HasColumnName("is_hidden"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tags"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("ix_tags_name"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tags_user_id"); + + b.ToTable("tags"); + }); + + modelBuilder.Entity("YABA.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Auth0Id") + .IsRequired() + .HasColumnType("text") + .HasColumnName("auth0id"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_on"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_modified"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("Auth0Id") + .IsUnique() + .HasDatabaseName("ix_users_auth0id"); + + b.ToTable("users"); + }); + + modelBuilder.Entity("YABA.Models.Bookmark", b => + { + b.HasOne("YABA.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_bookmarks_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("YABA.Models.BookmarkTag", b => + { + b.HasOne("YABA.Models.Bookmark", "Bookmark") + .WithMany() + .HasForeignKey("BookmarkId") + .HasConstraintName("fk_bookmark_tags_bookmarks_bookmark_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("YABA.Models.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .HasConstraintName("fk_bookmark_tags_tags_tag_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Bookmark"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("YABA.Models.Tag", b => + { + b.HasOne("YABA.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_tags_users_user_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/YABA.Data/Migrations/20230128064541_ModifiedBookmarkTagsPK_RemovedSoftDeleteFromTagsAndBookmarks.cs b/YABA.Data/Migrations/20230128064541_ModifiedBookmarkTagsPK_RemovedSoftDeleteFromTagsAndBookmarks.cs new file mode 100644 index 0000000..a267588 --- /dev/null +++ b/YABA.Data/Migrations/20230128064541_ModifiedBookmarkTagsPK_RemovedSoftDeleteFromTagsAndBookmarks.cs @@ -0,0 +1,86 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace YABA.Data.Migrations +{ + public partial class ModifiedBookmarkTagsPK_RemovedSoftDeleteFromTagsAndBookmarks : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "pk_bookmark_tags", + table: "bookmark_tags"); + + migrationBuilder.DropIndex( + name: "ix_bookmark_tags_bookmark_id_tag_id", + table: "bookmark_tags"); + + migrationBuilder.DropColumn( + name: "is_deleted", + table: "tags"); + + migrationBuilder.DropColumn( + name: "is_deleted", + table: "bookmarks"); + + migrationBuilder.DropColumn( + name: "id", + table: "bookmark_tags"); + + migrationBuilder.AddPrimaryKey( + name: "pk_bookmark_tags", + table: "bookmark_tags", + columns: new[] { "bookmark_id", "tag_id" }); + + migrationBuilder.CreateIndex( + name: "ix_tags_name", + table: "tags", + column: "name", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "ix_tags_name", + table: "tags"); + + migrationBuilder.DropPrimaryKey( + name: "pk_bookmark_tags", + table: "bookmark_tags"); + + migrationBuilder.AddColumn( + name: "is_deleted", + table: "tags", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "is_deleted", + table: "bookmarks", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "id", + table: "bookmark_tags", + type: "integer", + nullable: false, + defaultValue: 0) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + migrationBuilder.AddPrimaryKey( + name: "pk_bookmark_tags", + table: "bookmark_tags", + column: "id"); + + migrationBuilder.CreateIndex( + name: "ix_bookmark_tags_bookmark_id_tag_id", + table: "bookmark_tags", + columns: new[] { "bookmark_id", "tag_id" }, + unique: true); + } + } +} diff --git a/YABA.Data/Migrations/YABABaseContextModelSnapshot.cs b/YABA.Data/Migrations/YABABaseContextModelSnapshot.cs index 0ea0f29..49f0d9b 100644 --- a/YABA.Data/Migrations/YABABaseContextModelSnapshot.cs +++ b/YABA.Data/Migrations/YABABaseContextModelSnapshot.cs @@ -36,10 +36,6 @@ namespace YABA.Data.Migrations .HasColumnType("text") .HasColumnName("description"); - b.Property("IsDeleted") - .HasColumnType("boolean") - .HasColumnName("is_deleted"); - b.Property("IsHidden") .HasColumnType("boolean") .HasColumnName("is_hidden"); @@ -58,6 +54,11 @@ namespace YABA.Data.Migrations .HasColumnType("text") .HasColumnName("title"); + b.Property("Url") + .IsRequired() + .HasColumnType("text") + .HasColumnName("url"); + b.Property("UserId") .HasColumnType("integer") .HasColumnName("user_id"); @@ -73,12 +74,6 @@ namespace YABA.Data.Migrations modelBuilder.Entity("YABA.Models.BookmarkTag", b => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("BookmarkId") .HasColumnType("integer") .HasColumnName("bookmark_id"); @@ -87,16 +82,12 @@ namespace YABA.Data.Migrations .HasColumnType("integer") .HasColumnName("tag_id"); - b.HasKey("Id") + b.HasKey("BookmarkId", "TagId") .HasName("pk_bookmark_tags"); b.HasIndex("TagId") .HasDatabaseName("ix_bookmark_tags_tag_id"); - b.HasIndex("BookmarkId", "TagId") - .IsUnique() - .HasDatabaseName("ix_bookmark_tags_bookmark_id_tag_id"); - b.ToTable("bookmark_tags"); }); @@ -108,10 +99,6 @@ namespace YABA.Data.Migrations .HasColumnName("id") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("IsDeleted") - .HasColumnType("boolean") - .HasColumnName("is_deleted"); - b.Property("IsHidden") .HasColumnType("boolean") .HasColumnName("is_hidden"); @@ -128,6 +115,10 @@ namespace YABA.Data.Migrations b.HasKey("Id") .HasName("pk_tags"); + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("ix_tags_name"); + b.HasIndex("UserId") .HasDatabaseName("ix_tags_user_id"); diff --git a/YABA.Data/YABA.Data.csproj b/YABA.Data/YABA.Data.csproj index bff9fc3..923a21d 100644 --- a/YABA.Data/YABA.Data.csproj +++ b/YABA.Data/YABA.Data.csproj @@ -18,6 +18,7 @@ + diff --git a/YABA.Models/Bookmark.cs b/YABA.Models/Bookmark.cs index 323478f..5a7dacf 100644 --- a/YABA.Models/Bookmark.cs +++ b/YABA.Models/Bookmark.cs @@ -1,20 +1,21 @@ using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using YABA.Common.Interfaces; using YABA.Models.Interfaces; namespace YABA.Models { - public class Bookmark : IIdentifiable, ISoftDeletable, IDateCreatedTrackable, IDateModifiedTrackable + public class Bookmark : IIdentifiable, IDateCreatedTrackable, IDateModifiedTrackable, IBookmark { public int Id { get; set; } - public bool IsDeleted { get; set; } public DateTimeOffset CreatedOn { get; set; } public DateTimeOffset LastModified { get; set; } public string Title { get; set; } public string Description { get; set; } public string Note { get; set; } public bool IsHidden { get; set; } + public string Url { get; set; } [Required] [ForeignKey(nameof(User))] diff --git a/YABA.Models/BookmarkTag.cs b/YABA.Models/BookmarkTag.cs index 3489c9f..0ffbfd1 100644 --- a/YABA.Models/BookmarkTag.cs +++ b/YABA.Models/BookmarkTag.cs @@ -1,13 +1,10 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using YABA.Models.Interfaces; namespace YABA.Models { - public class BookmarkTag : IIdentifiable + public class BookmarkTag { - public int Id { get; set; } - [Required] [ForeignKey(nameof(Bookmark))] public int BookmarkId { get; set; } diff --git a/YABA.Models/Tag.cs b/YABA.Models/Tag.cs index a203c24..64c328d 100644 --- a/YABA.Models/Tag.cs +++ b/YABA.Models/Tag.cs @@ -4,10 +4,9 @@ using YABA.Models.Interfaces; namespace YABA.Models { - public class Tag : IIdentifiable, ISoftDeletable + public class Tag : IIdentifiable { public int Id { get; set; } - public bool IsDeleted { get; set; } public string Name { get; set; } public bool IsHidden { get; set; } diff --git a/YABA.Models/YABA.Models.csproj b/YABA.Models/YABA.Models.csproj index 344117a..fecb72f 100644 --- a/YABA.Models/YABA.Models.csproj +++ b/YABA.Models/YABA.Models.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/YABA.Service/BookmarkService.cs b/YABA.Service/BookmarkService.cs new file mode 100644 index 0000000..1f00a3d --- /dev/null +++ b/YABA.Service/BookmarkService.cs @@ -0,0 +1,232 @@ +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; + _mapper = mapper; + } + + public CrudResultDTO> GetAll() + { + var currentUserId = GetCurrentUserId(); + + var bookmarkTagsLookup = _roContext.BookmarkTags + .Where(x => x.Bookmark.UserId == currentUserId) + .GroupBy(x => x.BookmarkId) + .ToDictionary(key => key.Key, value => value.Select(x => x.TagId)); + + var bookmarksLookup = _roContext.Bookmarks + .Where(x => bookmarkTagsLookup.Keys.Contains(x.Id)) + .ToDictionary(key => key.Id, value => value); + + var tagsLookup = _roContext.Tags + .Where(x => bookmarkTagsLookup.Values.SelectMany(y => y.ToList()).Contains(x.Id)) + .ToDictionary(key => key.Id, value => value); + + var bookmarks = new List(); + + foreach(var bookmarkTagLookup in bookmarkTagsLookup) + { + var bookmarkExists = bookmarksLookup.TryGetValue(bookmarkTagLookup.Key, out Bookmark bookmark); + + if (!bookmarkExists) continue; + + var bookmarkDTO = _mapper.Map(bookmark); + + foreach(var tagId in bookmarkTagLookup.Value) + { + var tagExists = tagsLookup.TryGetValue(tagId, out Tag tag); + + if (!tagExists) continue; + + var tagDTO = _mapper.Map(tag); + bookmarkDTO.Tags.Add(tagDTO); + } + + bookmarks.Add(bookmarkDTO); + } + + return new CrudResultDTO> { Entry = bookmarks, 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(); + + if (!_roContext.Users.UserExists(currentUserId)) return crudResult; + + 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; + bookmark.Url = request.Url; + + 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 }); + await _context.Tags.AddRangeAsync(tagsToSave); + + if (await _context.SaveChangesAsync() <= 0) return crudResults; + + // Add newly added tags to the lookup + savedUserTags.AddRange(tagsToSave); + + var existingBookmarkTags = _context.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; + } + } +} diff --git a/YABA.Service/Configuration/AutoMapperProfile.cs b/YABA.Service/Configuration/AutoMapperProfile.cs index 641e1e3..076b41c 100644 --- a/YABA.Service/Configuration/AutoMapperProfile.cs +++ b/YABA.Service/Configuration/AutoMapperProfile.cs @@ -1,5 +1,7 @@ using AutoMapper; using YABA.Common.DTOs; +using YABA.Common.DTOs.Bookmarks; +using YABA.Common.DTOs.Tags; using YABA.Models; namespace YABA.Service.Configuration @@ -9,6 +11,11 @@ namespace YABA.Service.Configuration public AutoMapperProfile() { CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); } } } diff --git a/YABA.Service/Configuration/DependencyInjectionConfiguration.cs b/YABA.Service/Configuration/DependencyInjectionConfiguration.cs index 36bc9a2..3d23a9b 100644 --- a/YABA.Service/Configuration/DependencyInjectionConfiguration.cs +++ b/YABA.Service/Configuration/DependencyInjectionConfiguration.cs @@ -9,6 +9,7 @@ namespace YABA.Service.Configuration public static void AddServiceProjectDependencyInjectionConfiguration(this IServiceCollection services, IConfiguration configuration) { services.AddScoped(); + services.AddScoped(); } } } diff --git a/YABA.Service/Interfaces/IBookmarkService.cs b/YABA.Service/Interfaces/IBookmarkService.cs new file mode 100644 index 0000000..924a9fe --- /dev/null +++ b/YABA.Service/Interfaces/IBookmarkService.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using YABA.Common.DTOs; +using YABA.Common.DTOs.Bookmarks; +using YABA.Common.DTOs.Tags; + +namespace YABA.Service.Interfaces +{ + public interface IBookmarkService + { + Task> CreateBookmark(CreateBookmarkRequestDTO request); + Task> UpdateBookmark(int id, UpdateBookmarkRequestDTO request); + Task>> UpdateBookmarkTags(int id, IEnumerable tags); + CrudResultDTO> GetAll(); + Task> Get(int id); + CrudResultDTO> GetBookmarkTags(int id); + Task> DeleteBookmark(int id); + Task>> DeleteBookmarks(IEnumerable ids); + + } +} diff --git a/YABA.Service/YABA.Service.csproj b/YABA.Service/YABA.Service.csproj index 3cf02c9..846cf6b 100644 --- a/YABA.Service/YABA.Service.csproj +++ b/YABA.Service/YABA.Service.csproj @@ -7,6 +7,7 @@ +