Add sorting columns
This commit is contained in:
parent
adf24cbd5c
commit
90adbcfb7c
|
@ -25,8 +25,8 @@ This is a rewrite of my [New Tab Page project](https://github.com/NeilBrommer/Ne
|
|||
- [x] Delete
|
||||
- [ ] Edit
|
||||
- [ ] Manage bookmarks
|
||||
- [ ] Create
|
||||
- [ ] Delete
|
||||
- [x] Create
|
||||
- [x] Delete
|
||||
- [ ] Edit
|
||||
- [x] Use [Refit](https://github.com/reactiveui/refit) for strongly typed API calls
|
||||
- [ ] Support choosing between storing data on the server or in IndexedDB
|
||||
|
|
|
@ -73,13 +73,13 @@
|
|||
</Dialog>
|
||||
|
||||
@code {
|
||||
protected BookmarkDto Model { get; set; } = new BookmarkDto("", "", null, 0);
|
||||
protected BookmarkDto Model { get; set; } = new BookmarkDto("", "", null, 0, 0);
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
this.Model = new BookmarkDto("", "", null, this.state.Value.GroupId);
|
||||
this.Model = new BookmarkDto("", "", null, 0, this.state.Value.GroupId);
|
||||
|
||||
actionSubscriber.SubscribeToAction<ShowCreateBookmarkFormAction>(this,
|
||||
a => this.Model.BookmarkGroupId = a.GroupId);
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
[Parameter]
|
||||
public EventCallback<BookmarkContainerDto> OnCreated { get; set; }
|
||||
|
||||
private BookmarkContainerDto Model { get; set; } = new BookmarkContainerDto("");
|
||||
private BookmarkContainerDto Model { get; set; } = new BookmarkContainerDto("", 0);
|
||||
|
||||
protected void OnSubmit()
|
||||
{
|
||||
|
|
|
@ -63,13 +63,13 @@
|
|||
</Dialog>
|
||||
|
||||
@code {
|
||||
private BookmarkGroupDto Model { get; set; } = new("", "", 0);
|
||||
private BookmarkGroupDto Model { get; set; } = new("", "", 0, 0);
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
this.Model = new BookmarkGroupDto("", "", state.Value.ContainerId);
|
||||
this.Model = new BookmarkGroupDto("", "", 0, state.Value.ContainerId);
|
||||
|
||||
// Keep the model's container ID up to date
|
||||
actionSubscriber.SubscribeToAction<ShowCreateGroupFormAction>(this,
|
||||
|
|
|
@ -21,7 +21,9 @@ namespace Start.Client.Store.Features.ContainersList {
|
|||
RecievedContainerListAction action) {
|
||||
return state with {
|
||||
ContainerListState = state.ContainerListState with {
|
||||
Containers = action.Containers.ToImmutableList(),
|
||||
Containers = action.Containers
|
||||
.SortContainers()
|
||||
.ToImmutableList(),
|
||||
IsLoadingContainersList = false,
|
||||
ErrorMessage = null
|
||||
}
|
||||
|
@ -43,7 +45,10 @@ namespace Start.Client.Store.Features.ContainersList {
|
|||
AddContainerToListAction action) {
|
||||
return state with {
|
||||
ContainerListState = state.ContainerListState with {
|
||||
Containers = state.ContainerListState.Containers.Add(action.NewContainer)
|
||||
Containers = state.ContainerListState.Containers
|
||||
.Add(action.NewContainer)
|
||||
.SortContainers()
|
||||
.ToImmutableList()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -55,6 +60,7 @@ namespace Start.Client.Store.Features.ContainersList {
|
|||
ContainerListState = state.ContainerListState with {
|
||||
Containers = state.ContainerListState.Containers
|
||||
.Where(c => c.BookmarkContainerId != action.ContainerIdToRemove)
|
||||
.SortContainers()
|
||||
.ToImmutableList()
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Fluxor;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
|
||||
using Start.Shared.Api;
|
||||
|
@ -7,9 +8,12 @@ using Start.Client.Store.Features.CurrentContainer;
|
|||
namespace Start.Client.Store.Features.CreateBookmark {
|
||||
public class CreateBookmarkEffects {
|
||||
public IBookmarksApi BookmarksApi { get; init; }
|
||||
public IBookmarkGroupsApi BookmarkGroupsApi { get; init; }
|
||||
|
||||
public CreateBookmarkEffects(IBookmarksApi bookmarksApi) {
|
||||
public CreateBookmarkEffects(IBookmarksApi bookmarksApi,
|
||||
IBookmarkGroupsApi bookmarkGroupsApi) {
|
||||
this.BookmarksApi = bookmarksApi;
|
||||
this.BookmarkGroupsApi = bookmarkGroupsApi;
|
||||
}
|
||||
|
||||
[EffectMethod]
|
||||
|
@ -18,9 +22,25 @@ namespace Start.Client.Store.Features.CreateBookmark {
|
|||
dispatch.Dispatch(new FetchCreateBookmarkAction());
|
||||
|
||||
try {
|
||||
Refit.ApiResponse<Start.Shared.BookmarkGroupDto?>? groupResponse = await this
|
||||
.BookmarkGroupsApi
|
||||
.GetBookmarkGroup(action.NewBookmark.BookmarkGroupId);
|
||||
|
||||
if (groupResponse == null || groupResponse.Content == null) {
|
||||
dispatch.Dispatch(new ErrorFetchingCreateBookmarkAction(
|
||||
"There was an error checking the bookmark group"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the sort order to highest in the group + 1
|
||||
// .Max throws an exception if Bookmarks is empty
|
||||
int sortOrder = !(groupResponse.Content.Bookmarks?.Any() ?? false)
|
||||
? 0
|
||||
: groupResponse.Content.Bookmarks.Max(b => b.SortOrder) + 1;
|
||||
|
||||
Refit.ApiResponse<Start.Shared.BookmarkDto?>? apiResponse = await this.BookmarksApi
|
||||
.CreateBookmark(action.NewBookmark.Title, action.NewBookmark.Url,
|
||||
action.NewBookmark.Notes, action.NewBookmark.BookmarkGroupId);
|
||||
action.NewBookmark.Notes, sortOrder, action.NewBookmark.BookmarkGroupId);
|
||||
|
||||
if (!apiResponse.IsSuccessStatusCode) {
|
||||
dispatch.Dispatch(new ErrorFetchingCreateBookmarkAction(
|
||||
|
|
|
@ -5,6 +5,8 @@ using Refit;
|
|||
using Start.Shared;
|
||||
using Start.Shared.Api;
|
||||
using Start.Client.Store.Features.ContainersList;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Start.Client.Store.Features.CreateContainer {
|
||||
public class CreateContainerEffects {
|
||||
|
@ -20,8 +22,22 @@ namespace Start.Client.Store.Features.CreateContainer {
|
|||
dispatch.Dispatch(new FetchCreateContainerAction());
|
||||
|
||||
try {
|
||||
ApiResponse<IEnumerable<BookmarkContainerDto>>? containersResponse = await this
|
||||
.BookmarkContainersApi
|
||||
.GetAllBookmarkContainers();
|
||||
|
||||
if (containersResponse == null || containersResponse.Content == null) {
|
||||
dispatch.Dispatch(new ErrorFetchingCreateContainerAction(
|
||||
"There was an error checking bookmark containers"));
|
||||
return;
|
||||
}
|
||||
|
||||
int sortOrder = !containersResponse.Content.Any()
|
||||
? 0
|
||||
: containersResponse.Content.Max(c => c.SortOrder) + 1;
|
||||
|
||||
ApiResponse<BookmarkContainerDto?> apiResponse = await this.BookmarkContainersApi
|
||||
.CreateBookmarkContainer(action.NewContainer.Title);
|
||||
.CreateBookmarkContainer(action.NewContainer.Title, sortOrder);
|
||||
|
||||
BookmarkContainerDto? container = apiResponse.Content;
|
||||
|
||||
|
|
|
@ -6,13 +6,17 @@ using Refit;
|
|||
using Start.Shared;
|
||||
using Start.Client.Store.Features.CurrentContainer;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Start.Client.Store.Features.CreateGroup {
|
||||
public class CreateGroupEffects {
|
||||
public IBookmarkGroupsApi BookmarkGroupsApi { get; init; }
|
||||
public IBookmarkContainersApi BookmarkContainersApi { get; init; }
|
||||
|
||||
public CreateGroupEffects(IBookmarkGroupsApi bookmarksApi) {
|
||||
public CreateGroupEffects(IBookmarkGroupsApi bookmarksApi,
|
||||
IBookmarkContainersApi bookmarkContainersApi) {
|
||||
this.BookmarkGroupsApi = bookmarksApi;
|
||||
this.BookmarkContainersApi = bookmarkContainersApi;
|
||||
}
|
||||
|
||||
[EffectMethod]
|
||||
|
@ -21,8 +25,22 @@ namespace Start.Client.Store.Features.CreateGroup {
|
|||
dispatch.Dispatch(new FetchCreateGroupAction());
|
||||
|
||||
try {
|
||||
ApiResponse<BookmarkContainerDto?>? containerResponse = await this
|
||||
.BookmarkContainersApi
|
||||
.GetBookmarkContainer(action.NewGroup.BookmarkContainerId);
|
||||
|
||||
if (containerResponse == null || containerResponse.Content == null) {
|
||||
dispatch.Dispatch(new ErrorFetchingCreateGroupAction(
|
||||
"There was an error checking the new group's bookmark container"));
|
||||
return;
|
||||
}
|
||||
|
||||
int sortOrder = !(containerResponse.Content.BookmarkGroups?.Any() ?? false)
|
||||
? 0
|
||||
: containerResponse.Content.BookmarkGroups.Max(g => g.SortOrder) + 1;
|
||||
|
||||
ApiResponse<BookmarkGroupDto?> apiResponse = await this.BookmarkGroupsApi
|
||||
.CreateBookmarkGroup(action.NewGroup.Title, action.NewGroup.Color,
|
||||
.CreateBookmarkGroup(action.NewGroup.Title, action.NewGroup.Color, sortOrder,
|
||||
action.NewGroup.BookmarkContainerId);
|
||||
|
||||
Console.WriteLine("Status code: " + apiResponse.StatusCode);
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
|||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
if (!this.RootState.Value.ContainerListState.Containers.Any()) {
|
||||
dispatch.Dispatch(new SubmitCreateContainerAction(
|
||||
new BookmarkContainerDto("Default")));
|
||||
new BookmarkContainerDto("Default", 0)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,11 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
|||
[ReducerMethod]
|
||||
public static RootState ReceivedCurrentContainer(RootState state,
|
||||
ReceivedCurrentContainerAction action) {
|
||||
BookmarkContainerDto? container = action.BookmarkContainer;
|
||||
container.BookmarkGroups = container.BookmarkGroups
|
||||
?.SortGroups()
|
||||
.ToList();
|
||||
|
||||
return state with {
|
||||
CurrentContainerState = state.CurrentContainerState with {
|
||||
Container = action.BookmarkContainer,
|
||||
|
@ -54,8 +59,9 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
|||
return state with {
|
||||
CurrentContainerState = state.CurrentContainerState with {
|
||||
Container = new BookmarkContainerDto(container.BookmarkContainerId,
|
||||
container.Title, container.BookmarkGroups?
|
||||
container.Title, container.SortOrder, container.BookmarkGroups?
|
||||
.Concat(new List<BookmarkGroupDto> { action.BookmarkGroup })
|
||||
.SortGroups()
|
||||
.ToList())
|
||||
}
|
||||
};
|
||||
|
@ -72,7 +78,7 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
|||
return state with {
|
||||
CurrentContainerState = state.CurrentContainerState with {
|
||||
Container = new BookmarkContainerDto(container.BookmarkContainerId,
|
||||
container.Title, container.BookmarkGroups?
|
||||
container.Title, container.SortOrder, container.BookmarkGroups?
|
||||
.Where(g => g.BookmarkGroupId != action.BookmarkGroupId)
|
||||
.ToList())
|
||||
}
|
||||
|
@ -90,9 +96,9 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
|||
?.Select(bg => {
|
||||
if (bg.BookmarkGroupId == action.Bookmark.BookmarkGroupId) {
|
||||
return new BookmarkGroupDto(bg.BookmarkGroupId, bg.Title, bg.Color,
|
||||
bg.BookmarkContainerId,
|
||||
bg.Bookmarks?
|
||||
bg.SortOrder, bg.BookmarkContainerId, bg.Bookmarks?
|
||||
.Concat(new List<BookmarkDto> { action.Bookmark })
|
||||
.SortBookmarks()
|
||||
.ToList());
|
||||
}
|
||||
|
||||
|
@ -103,7 +109,7 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
|||
return state with {
|
||||
CurrentContainerState = state.CurrentContainerState with {
|
||||
Container = new BookmarkContainerDto(container.BookmarkContainerId,
|
||||
container.Title, groups)
|
||||
container.Title, container.SortOrder, groups)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -117,7 +123,7 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
|||
|
||||
List<BookmarkGroupDto>? groups = container.BookmarkGroups
|
||||
?.Select(bg => new BookmarkGroupDto(bg.BookmarkGroupId, bg.Title, bg.Color,
|
||||
bg.BookmarkContainerId, bg.Bookmarks
|
||||
bg.SortOrder, bg.BookmarkContainerId, bg.Bookmarks
|
||||
?.Where(b => b.BookmarkId != action.BookmarkId)
|
||||
.ToList()))
|
||||
.ToList();
|
||||
|
@ -125,7 +131,7 @@ namespace Start.Client.Store.Features.CurrentContainer {
|
|||
return state with {
|
||||
CurrentContainerState = state.CurrentContainerState with {
|
||||
Container = new BookmarkContainerDto(container.BookmarkContainerId,
|
||||
container.Title, groups)
|
||||
container.Title, container.SortOrder, groups)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -54,9 +54,9 @@ namespace Start.Server.Controllers {
|
|||
[Route("Create")]
|
||||
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(BookmarkContainerDto))]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> CreateBookmarkContainer([FromBody] string title) {
|
||||
public async Task<IActionResult> CreateBookmarkContainer(string title, int sortOrder) {
|
||||
BookmarkContainerDto? container = (await this.bookmarkContainerService
|
||||
.CreateBookmarkContainer(this.GetAuthorizedUserId(), title))
|
||||
.CreateBookmarkContainer(this.GetAuthorizedUserId(), title, sortOrder))
|
||||
?.MapToDto();
|
||||
|
||||
if (container == null)
|
||||
|
|
|
@ -37,9 +37,9 @@ namespace Start.Server.Controllers {
|
|||
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(BookmarkGroupDto))]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> CreateBookmarkGroup(string title, string color,
|
||||
int bookmarkContainerId) {
|
||||
int sortOrder, int bookmarkContainerId) {
|
||||
BookmarkGroup? newGroup = await this.bookmarkGroupService
|
||||
.CreateBookmarkGroup(this.GetAuthorizedUserId(), title, color, bookmarkContainerId);
|
||||
.CreateBookmarkGroup(this.GetAuthorizedUserId(), title, color, sortOrder, bookmarkContainerId);
|
||||
|
||||
if (newGroup == null)
|
||||
return BadRequest();
|
||||
|
|
|
@ -37,9 +37,10 @@ namespace Start.Server.Controllers {
|
|||
[ProducesResponseType(StatusCodes.Status201Created, Type = typeof(BookmarkDto))]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> CreateBookmark(string title, string url, string? notes,
|
||||
int bookmarkGroupId) {
|
||||
int sortOrder, int bookmarkGroupId) {
|
||||
BookmarkDto? bookmark = (await this.bookmarkService
|
||||
.CreateBookmark(this.GetAuthorizedUserId(), title, url, notes, bookmarkGroupId))
|
||||
.CreateBookmark(this.GetAuthorizedUserId(), title, url, notes, sortOrder,
|
||||
bookmarkGroupId))
|
||||
?.MapToDto();
|
||||
|
||||
if (bookmark == null)
|
||||
|
|
508
Start/Server/Data/Migrations/20211220002043_AddSorting.Designer.cs
generated
Normal file
508
Start/Server/Data/Migrations/20211220002043_AddSorting.Designer.cs
generated
Normal file
|
@ -0,0 +1,508 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Start.Server.Data;
|
||||
|
||||
namespace Start.Server.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20211220002043_AddSorting")]
|
||||
partial class AddSorting
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.11");
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
|
||||
{
|
||||
b.Property<string>("UserCode")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DeviceCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserCode");
|
||||
|
||||
b.HasIndex("DeviceCode")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.ToTable("DeviceCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("ConsumedTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.HasIndex("SubjectId", "ClientId", "Type");
|
||||
|
||||
b.HasIndex("SubjectId", "SessionId", "Type");
|
||||
|
||||
b.ToTable("PersistedGrants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.Bookmark", b =>
|
||||
{
|
||||
b.Property<int>("BookmarkId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookmarkGroupId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(5000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookmarkId");
|
||||
|
||||
b.HasIndex("BookmarkGroupId");
|
||||
|
||||
b.ToTable("Bookmarks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.BookmarkContainer", b =>
|
||||
{
|
||||
b.Property<int>("BookmarkContainerId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ApplicationUserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookmarkContainerId");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("BookmarkContainers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.BookmarkGroup", b =>
|
||||
{
|
||||
b.Property<int>("BookmarkGroupId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookmarkContainerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.IsRequired()
|
||||
.HasMaxLength(6)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookmarkGroupId");
|
||||
|
||||
b.HasIndex("BookmarkContainerId");
|
||||
|
||||
b.ToTable("BookmarkGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Start.Server.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Start.Server.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Start.Server.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Start.Server.Models.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.Bookmark", b =>
|
||||
{
|
||||
b.HasOne("Start.Server.Models.BookmarkGroup", "BookmarkGroup")
|
||||
.WithMany("Bookmarks")
|
||||
.HasForeignKey("BookmarkGroupId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BookmarkGroup");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.BookmarkContainer", b =>
|
||||
{
|
||||
b.HasOne("Start.Server.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("BookmarkContainers")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApplicationUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.BookmarkGroup", b =>
|
||||
{
|
||||
b.HasOne("Start.Server.Models.BookmarkContainer", "BookmarkContainer")
|
||||
.WithMany("BookmarkGroups")
|
||||
.HasForeignKey("BookmarkContainerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BookmarkContainer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("BookmarkContainers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.BookmarkContainer", b =>
|
||||
{
|
||||
b.Navigation("BookmarkGroups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Start.Server.Models.BookmarkGroup", b =>
|
||||
{
|
||||
b.Navigation("Bookmarks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
42
Start/Server/Data/Migrations/20211220002043_AddSorting.cs
Normal file
42
Start/Server/Data/Migrations/20211220002043_AddSorting.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Start.Server.Data.Migrations {
|
||||
public partial class AddSorting : Migration {
|
||||
protected override void Up(MigrationBuilder migrationBuilder) {
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SortOrder",
|
||||
table: "Bookmarks",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SortOrder",
|
||||
table: "BookmarkGroups",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SortOrder",
|
||||
table: "BookmarkContainers",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) {
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SortOrder",
|
||||
table: "Bookmarks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SortOrder",
|
||||
table: "BookmarkGroups");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SortOrder",
|
||||
table: "BookmarkContainers");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -328,6 +328,9 @@ namespace Start.Server.Data.Migrations
|
|||
.HasMaxLength(5000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
|
@ -355,8 +358,12 @@ namespace Start.Server.Data.Migrations
|
|||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookmarkContainerId");
|
||||
|
@ -380,6 +387,9 @@ namespace Start.Server.Data.Migrations
|
|||
.HasMaxLength(6)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
|
|
|
@ -46,10 +46,28 @@ namespace Start.Server.Data.Services {
|
|||
}
|
||||
|
||||
public async Task<BookmarkContainer?> CreateBookmarkContainer(string userId,
|
||||
string title) {
|
||||
string title, int sortOrder) {
|
||||
// No need to worry about ownership here
|
||||
|
||||
BookmarkContainer newContainer = new(userId, title);
|
||||
// Increase the sorting ID for these items if it's needed to make room for this item
|
||||
List<BookmarkContainer>? containers = this.db.BookmarkContainers
|
||||
.Where(bc => bc.ApplicationUserId == userId)
|
||||
.SortContainers()
|
||||
.ToList();
|
||||
|
||||
if (containers == null)
|
||||
return null;
|
||||
|
||||
// Fix up sort order just in case
|
||||
for (int i = 0; i < containers.Count; i++) {
|
||||
containers[i].SortOrder = i;
|
||||
|
||||
// Make room for the new container
|
||||
if (i >= sortOrder)
|
||||
containers[i].SortOrder++;
|
||||
}
|
||||
|
||||
BookmarkContainer newContainer = new(userId, title, sortOrder);
|
||||
await this.db.BookmarkContainers.AddAsync(newContainer);
|
||||
await this.db.SaveChangesAsync();
|
||||
return newContainer;
|
||||
|
@ -57,15 +75,39 @@ namespace Start.Server.Data.Services {
|
|||
|
||||
public async Task<BookmarkContainer?> UpdateBookmarkContainer(string userId,
|
||||
BookmarkContainer bookmarkContainer) {
|
||||
BookmarkContainer? exitingBookmarkContainer = await this.db.BookmarkContainers
|
||||
.SingleOrDefaultAsync(bc => bc.BookmarkContainerId
|
||||
== bookmarkContainer.BookmarkContainerId);
|
||||
BookmarkContainer? existingBookmarkContainer = await this.db.BookmarkContainers
|
||||
.SingleOrDefaultAsync(bc =>
|
||||
bc.BookmarkContainerId == bookmarkContainer.BookmarkContainerId);
|
||||
|
||||
if (exitingBookmarkContainer == null
|
||||
if (existingBookmarkContainer == null
|
||||
|| !BookmarkOwnershipTools
|
||||
.IsBookmarkContainerOwner(this.db, userId, bookmarkContainer.BookmarkContainerId))
|
||||
return null;
|
||||
|
||||
// If the sort order has changed, then the other containers need to be shuffled around
|
||||
if (existingBookmarkContainer.SortOrder < bookmarkContainer.SortOrder) {
|
||||
// The container has been moved to a higher sort order
|
||||
var affectedContainers = db.BookmarkContainers
|
||||
.Where(bc => bc.ApplicationUserId == userId)
|
||||
.Where(bc => bc.SortOrder > existingBookmarkContainer.SortOrder)
|
||||
.Where(bc => bc.SortOrder <= bookmarkContainer.SortOrder)
|
||||
.ToList();
|
||||
|
||||
affectedContainers.ForEach(bc => bc.SortOrder -= 1);
|
||||
// Let the save changes below save this
|
||||
}
|
||||
else if (existingBookmarkContainer.SortOrder > bookmarkContainer.SortOrder) {
|
||||
// The container has been moved to a lower sort order
|
||||
var affectedContainers = db.BookmarkContainers
|
||||
.Where(bc => bc.ApplicationUserId == userId)
|
||||
.Where(bc => bc.SortOrder < existingBookmarkContainer.SortOrder)
|
||||
.Where(bc => bc.SortOrder >= bookmarkContainer.SortOrder)
|
||||
.ToList();
|
||||
|
||||
affectedContainers.ForEach(bc => bc.SortOrder += 1);
|
||||
// Let the save changes below save this
|
||||
}
|
||||
|
||||
this.db.Entry(bookmarkContainer).State = EntityState.Modified;
|
||||
await this.db.SaveChangesAsync();
|
||||
|
||||
|
@ -86,6 +128,22 @@ namespace Start.Server.Data.Services {
|
|||
this.db.BookmarkContainers.Remove(bookmarkContainer);
|
||||
await this.db.SaveChangesAsync();
|
||||
|
||||
List<BookmarkContainer>? containers = this.db.BookmarkContainers
|
||||
.Where(bc => bc.ApplicationUserId == userId)
|
||||
.SortContainers()
|
||||
.ToList();
|
||||
|
||||
if (containers == null)
|
||||
// The container *was* deleted, so indicate as such
|
||||
return true;
|
||||
|
||||
// Fix up sort order just in case
|
||||
for (int i = 0; i < containers.Count; i++) {
|
||||
containers[i].SortOrder = i;
|
||||
}
|
||||
|
||||
await this.db.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,12 +36,12 @@ namespace Start.Server.Data.Services {
|
|||
}
|
||||
|
||||
public async Task<BookmarkGroup?> CreateBookmarkGroup(string userId, string title,
|
||||
string color, int bookmarkContainerId) {
|
||||
string color, int sortOrder, int bookmarkContainerId) {
|
||||
if (!BookmarkOwnershipTools
|
||||
.IsBookmarkContainerOwner(this.db, userId, bookmarkContainerId))
|
||||
return null;
|
||||
|
||||
BookmarkGroup newBookmarkGroup = new(title, color, bookmarkContainerId);
|
||||
BookmarkGroup newBookmarkGroup = new(title, color, sortOrder, bookmarkContainerId);
|
||||
await this.db.BookmarkGroups.AddAsync(newBookmarkGroup);
|
||||
await this.db.SaveChangesAsync();
|
||||
|
||||
|
|
|
@ -27,12 +27,12 @@ namespace Start.Server.Data.Services {
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Bookmark?> CreateBookmark(string userId, string title, string url, string? notes,
|
||||
int bookmarkGroupId) {
|
||||
public async Task<Bookmark?> CreateBookmark(string userId, string title, string url,
|
||||
string? notes, int sortOrder, int bookmarkGroupId) {
|
||||
if (!BookmarkOwnershipTools.IsBookmarkGroupOwner(this.db, userId, bookmarkGroupId))
|
||||
return null;
|
||||
|
||||
Bookmark newBookmark = new(title, url, notes, bookmarkGroupId);
|
||||
Bookmark newBookmark = new(title, url, notes, sortOrder, bookmarkGroupId);
|
||||
|
||||
await db.Bookmarks.AddAsync(newBookmark);
|
||||
await db.SaveChangesAsync();
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Start.Server.Data.Services.Interfaces {
|
|||
bool includeGroups = false, bool includeBookmarks = false);
|
||||
|
||||
public Task<BookmarkContainer?> CreateBookmarkContainer(string userId,
|
||||
string title);
|
||||
string title, int sortOrder);
|
||||
public Task<BookmarkContainer?> UpdateBookmarkContainer(string userId,
|
||||
BookmarkContainer bookmarkContainer);
|
||||
public Task<bool> DeleteBookmarkContainer(string userId, int bookmarkContainerId);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Start.Server.Models;
|
||||
using Start.Shared;
|
||||
|
||||
namespace Start.Server.Data.Services.Interfaces {
|
||||
public interface IBookmarkGroupService {
|
||||
|
@ -12,7 +10,7 @@ namespace Start.Server.Data.Services.Interfaces {
|
|||
bool includeBookmarks = false);
|
||||
|
||||
public Task<BookmarkGroup?> CreateBookmarkGroup(string userId, string title,
|
||||
string color, int bookmarkContainerId);
|
||||
string color, int sortOrder, int bookmarkContainerId);
|
||||
public Task<BookmarkGroup?> UpdateBookmarkGroup(string userId,
|
||||
BookmarkGroup bookmarkGroup);
|
||||
public Task<bool> DeleteBookmarkGroup(string userId, int bookmarkGroupId);
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Start.Server.Data.Services.Interfaces {
|
|||
public Task<IList<Bookmark>> GetUserBookmarks(string userId);
|
||||
|
||||
public Task<Bookmark?> CreateBookmark(string userId, string title, string url,
|
||||
string? notes, int bookmarkGroupId);
|
||||
string? notes, int sortOrder, int bookmarkGroupId);
|
||||
public Task<Bookmark?> UpdateBookmark(string userId, Bookmark bookmark);
|
||||
public Task<bool> DeleteBookmark(string userId, int bookmarkId);
|
||||
}
|
||||
|
|
|
@ -6,18 +6,18 @@ namespace Start.Server.Extensions {
|
|||
public static class BookmarkMaps {
|
||||
public static BookmarkDto MapToDto(this Bookmark bookmark) {
|
||||
return new BookmarkDto(bookmark.BookmarkId, bookmark.Title, bookmark.Url,
|
||||
bookmark.Notes, bookmark.BookmarkGroupId);
|
||||
bookmark.Notes, bookmark.SortOrder, bookmark.BookmarkGroupId);
|
||||
}
|
||||
|
||||
public static BookmarkGroupDto MapToDto(this BookmarkGroup bookmarkGroup) {
|
||||
return new BookmarkGroupDto(bookmarkGroup.BookmarkGroupId, bookmarkGroup.Title,
|
||||
bookmarkGroup.Color, bookmarkGroup.BookmarkContainerId,
|
||||
bookmarkGroup.Color, bookmarkGroup.SortOrder, bookmarkGroup.BookmarkContainerId,
|
||||
bookmarkGroup.Bookmarks?.Select(b => b.MapToDto()).ToList());
|
||||
}
|
||||
|
||||
public static BookmarkContainerDto MapToDto(this BookmarkContainer bookmarkContainer) {
|
||||
return new BookmarkContainerDto(bookmarkContainer.BookmarkContainerId,
|
||||
bookmarkContainer.Title,
|
||||
bookmarkContainer.Title, bookmarkContainer.SortOrder,
|
||||
bookmarkContainer.BookmarkGroups?.Select(bg => bg.MapToDto()).ToList());
|
||||
}
|
||||
}
|
||||
|
|
28
Start/Server/Extensions/SortingExtensions.cs
Normal file
28
Start/Server/Extensions/SortingExtensions.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Start.Server.Models;
|
||||
|
||||
namespace Start.Server.Extensions {
|
||||
public static class SortingExtensions {
|
||||
public static IEnumerable<BookmarkContainer> SortContainers(
|
||||
this IEnumerable<BookmarkContainer> bookmarkContainers) {
|
||||
return bookmarkContainers
|
||||
.OrderBy(bc => bc.SortOrder)
|
||||
.ThenBy(bc => bc.BookmarkContainerId);
|
||||
}
|
||||
|
||||
public static IEnumerable<BookmarkGroup> SortGroups(
|
||||
this IEnumerable<BookmarkGroup> bookmarkGroups) {
|
||||
return bookmarkGroups
|
||||
.OrderBy(bg => bg.SortOrder)
|
||||
.ThenBy(bg => bg.BookmarkGroupId);
|
||||
}
|
||||
|
||||
public static IEnumerable<Bookmark> SortBookmarks(
|
||||
this IEnumerable<Bookmark> bookmarks) {
|
||||
return bookmarks
|
||||
.OrderBy(b => b.SortOrder)
|
||||
.ThenBy(b => b.BookmarkId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Start.Server.Models {
|
||||
/// <summary>A bookmark with display text and a URL to link to</summary>
|
||||
|
@ -17,21 +16,24 @@ namespace Start.Server.Models {
|
|||
/// <summary>Arbitrary notes about the bookmark</summary>
|
||||
[MaxLength(5000)]
|
||||
public string? Notes { get; set; }
|
||||
/// <summary>Used for sorting lists of bookmarks</summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>The unique ID for the group the bookmark is in</summary>
|
||||
public int BookmarkGroupId { get; set; }
|
||||
/// <summary>The group the bookmark is in</summary>
|
||||
public BookmarkGroup? BookmarkGroup { get; set; }
|
||||
|
||||
public Bookmark(string title, string url, string? notes, int bookmarkGroupId) {
|
||||
public Bookmark(string title, string url, string? notes, int sortOrder, int bookmarkGroupId) {
|
||||
this.Title = title;
|
||||
this.Url = url;
|
||||
this.Notes = notes;
|
||||
this.SortOrder = sortOrder;
|
||||
this.BookmarkGroupId = bookmarkGroupId;
|
||||
}
|
||||
|
||||
public Bookmark(int bookmarkId, string title, string url, string? notes, int bookmarkGroupId)
|
||||
: this(title, url, notes, bookmarkGroupId) {
|
||||
public Bookmark(int bookmarkId, string title, string url, string? notes, int sortOrder,
|
||||
int bookmarkGroupId) : this(title, url, notes, sortOrder, bookmarkGroupId) {
|
||||
this.BookmarkId = bookmarkId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Start.Server.Models {
|
||||
|
@ -8,9 +7,11 @@ namespace Start.Server.Models {
|
|||
/// <summary>A unique ID for the container</summary>
|
||||
[Key]
|
||||
public int BookmarkContainerId { get; set; }
|
||||
|
||||
/// <summary>A title to disply to the user</summary>
|
||||
[MaxLength(300)]
|
||||
public string Title { get; set; }
|
||||
/// <summary>Used for sorting lists of bookmark containers</summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>The unique ID of the user that this container belongs to</summary>
|
||||
public string ApplicationUserId { get; set; }
|
||||
|
@ -20,13 +21,14 @@ namespace Start.Server.Models {
|
|||
/// <summary>The <see cref="BookmarkGroup"/>s in this container</summary>
|
||||
public List<BookmarkGroup>? BookmarkGroups { get; set; }
|
||||
|
||||
public BookmarkContainer(string applicationUserId, string title) {
|
||||
public BookmarkContainer(string applicationUserId, string title, int sortOrder) {
|
||||
this.ApplicationUserId = applicationUserId;
|
||||
this.Title = title;
|
||||
this.SortOrder = sortOrder;
|
||||
}
|
||||
|
||||
public BookmarkContainer(int bookmarkContainerId, string applicationUserId, string title)
|
||||
: this(applicationUserId, title) {
|
||||
public BookmarkContainer(int bookmarkContainerId, string applicationUserId, string title,
|
||||
int sortOrder) : this(applicationUserId, title, sortOrder) {
|
||||
this.BookmarkContainerId = bookmarkContainerId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Start.Server.Models {
|
||||
|
@ -15,6 +14,8 @@ namespace Start.Server.Models {
|
|||
/// <summary>A hex color for the group</summary>
|
||||
[MaxLength(6)]
|
||||
public string Color { get; set; }
|
||||
/// <summary>Used for sorting lists of bookmark groups</summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>The unique ID of the container this group is in</summary>
|
||||
public int BookmarkContainerId { get; set; }
|
||||
|
@ -24,14 +25,15 @@ namespace Start.Server.Models {
|
|||
/// <summary>The bookmarks in this group</summary>
|
||||
public List<Bookmark>? Bookmarks { get; set; }
|
||||
|
||||
public BookmarkGroup(string title, string color, int bookmarkContainerId) {
|
||||
public BookmarkGroup(string title, string color, int sortOrder, int bookmarkContainerId) {
|
||||
this.Title = title;
|
||||
this.Color = color;
|
||||
this.SortOrder = sortOrder;
|
||||
this.BookmarkContainerId = bookmarkContainerId;
|
||||
}
|
||||
|
||||
public BookmarkGroup(int bookmarkGroupId, string title, string color,
|
||||
int bookmarkContainerId) : this(title, color, bookmarkContainerId) {
|
||||
public BookmarkGroup(int bookmarkGroupId, string title, string color, int sortOrder,
|
||||
int bookmarkContainerId) : this(title, color, sortOrder, bookmarkContainerId) {
|
||||
this.BookmarkGroupId = bookmarkGroupId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ namespace Start.Shared.Api {
|
|||
Task<ApiResponse<BookmarkContainerDto?>> GetBookmarkContainer(int bookmarkContainerId);
|
||||
|
||||
[Post("/Create")]
|
||||
Task<ApiResponse<BookmarkContainerDto?>> CreateBookmarkContainer(
|
||||
[Body(BodySerializationMethod.Serialized)] string title);
|
||||
Task<ApiResponse<BookmarkContainerDto?>> CreateBookmarkContainer(string title,
|
||||
int sortOrder);
|
||||
|
||||
[Delete("/Delete/{bookmarkContainerId}")]
|
||||
Task<HttpResponseMessage> DeleteBookmarkContainer(int bookmarkContainerId);
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Start.Shared.Api {
|
|||
|
||||
[Post("/Create")]
|
||||
Task<ApiResponse<BookmarkGroupDto?>> CreateBookmarkGroup(string title, string color,
|
||||
int bookmarkContainerId);
|
||||
int sortOrder, int bookmarkContainerId);
|
||||
|
||||
[Delete("/Delete/{bookmarkGroupId}")]
|
||||
Task<HttpResponseMessage> DeleteBookmarkGroup(int bookmarkGroupId);
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Start.Shared.Api {
|
|||
|
||||
[Post("/Create")]
|
||||
Task<ApiResponse<BookmarkDto?>> CreateBookmark(string title, string url, string? notes,
|
||||
int bookmarkGroupId);
|
||||
int sortOrder, int bookmarkGroupId);
|
||||
|
||||
[Delete("/Delete/{bookmarkId}")]
|
||||
Task<HttpResponseMessage> DeleteBookmark(int bookmarkId);
|
||||
|
|
|
@ -8,20 +8,23 @@ namespace Start.Shared {
|
|||
[Required]
|
||||
[StringLength(300)]
|
||||
public string Title { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
public IList<BookmarkGroupDto>? BookmarkGroups { get; set; }
|
||||
|
||||
public BookmarkContainerDto(string title) {
|
||||
public BookmarkContainerDto(string title, int sortOrder) {
|
||||
this.Title = title;
|
||||
this.SortOrder = sortOrder;
|
||||
}
|
||||
|
||||
public BookmarkContainerDto(int bookmarkContainerId, string title) : this(title) {
|
||||
public BookmarkContainerDto(int bookmarkContainerId, string title, int sortOrder)
|
||||
: this(title, sortOrder) {
|
||||
this.BookmarkContainerId = bookmarkContainerId;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public BookmarkContainerDto(int bookmarkContainerId, string title,
|
||||
IList<BookmarkGroupDto>? bookmarkGroups) : this(bookmarkContainerId, title) {
|
||||
public BookmarkContainerDto(int bookmarkContainerId, string title, int sortOrder,
|
||||
IList<BookmarkGroupDto>? bookmarkGroups) : this(bookmarkContainerId, title, sortOrder) {
|
||||
this.BookmarkGroups = bookmarkGroups;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,20 +10,23 @@ namespace Start.Shared {
|
|||
public string Url { get; set; }
|
||||
[StringLength(5000)]
|
||||
public string? Notes { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
public int BookmarkGroupId { get; set; }
|
||||
|
||||
public BookmarkDto(string title, string url, string? notes, int bookmarkGroupId) {
|
||||
public BookmarkDto(string title, string url, string? notes, int sortOrder,
|
||||
int bookmarkGroupId) {
|
||||
this.Title = title;
|
||||
this.Url = url;
|
||||
this.Notes = notes;
|
||||
this.SortOrder = sortOrder;
|
||||
this.BookmarkGroupId = bookmarkGroupId;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public BookmarkDto(int bookmarkId, string title, string url, string? notes,
|
||||
public BookmarkDto(int bookmarkId, string title, string url, string? notes, int sortOrder,
|
||||
int bookmarkGroupId)
|
||||
: this(title, url, notes, bookmarkGroupId) {
|
||||
: this(title, url, notes, sortOrder, bookmarkGroupId) {
|
||||
this.BookmarkId = bookmarkId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,26 +11,29 @@ namespace Start.Shared {
|
|||
[Required(AllowEmptyStrings = false, ErrorMessage = "Color is required")]
|
||||
[StringLength(7)]
|
||||
public string Color { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public int BookmarkContainerId { get; set; }
|
||||
|
||||
public IList<BookmarkDto>? Bookmarks { get; set; }
|
||||
|
||||
public BookmarkGroupDto(string title, string color, int bookmarkContainerId) {
|
||||
public BookmarkGroupDto(string title, string color, int sortOrder,
|
||||
int bookmarkContainerId) {
|
||||
this.Title = title;
|
||||
this.Color = color;
|
||||
this.SortOrder = sortOrder;
|
||||
this.BookmarkContainerId = bookmarkContainerId;
|
||||
}
|
||||
|
||||
public BookmarkGroupDto(int bookmarkGroupId, string title, string color,
|
||||
public BookmarkGroupDto(int bookmarkGroupId, string title, string color, int sortOrder,
|
||||
int bookmarkContainerId)
|
||||
: this(title, color, bookmarkContainerId) {
|
||||
: this(title, color, sortOrder, bookmarkContainerId) {
|
||||
this.BookmarkGroupId = bookmarkGroupId;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public BookmarkGroupDto(int bookmarkGroupId, string title, string color,
|
||||
public BookmarkGroupDto(int bookmarkGroupId, string title, string color, int sortOrder,
|
||||
int bookmarkContainerId, IList<BookmarkDto>? bookmarks)
|
||||
: this(bookmarkGroupId, title, color, bookmarkContainerId) {
|
||||
: this(bookmarkGroupId, title, color, sortOrder, bookmarkContainerId) {
|
||||
this.Bookmarks = bookmarks;
|
||||
}
|
||||
}
|
||||
|
|
27
Start/Shared/SortingExtensions.cs
Normal file
27
Start/Shared/SortingExtensions.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Start.Shared {
|
||||
public static class SortingExtensions {
|
||||
public static IEnumerable<BookmarkContainerDto> SortContainers(
|
||||
this IEnumerable<BookmarkContainerDto> bookmarkContainers) {
|
||||
return bookmarkContainers
|
||||
.OrderBy(bc => bc.SortOrder)
|
||||
.ThenBy(bc => bc.BookmarkContainerId);
|
||||
}
|
||||
|
||||
public static IEnumerable<BookmarkGroupDto> SortGroups(
|
||||
this IEnumerable<BookmarkGroupDto> bookmarkGroups) {
|
||||
return bookmarkGroups
|
||||
.OrderBy(bg => bg.SortOrder)
|
||||
.ThenBy(bg => bg.BookmarkGroupId);
|
||||
}
|
||||
|
||||
public static IEnumerable<BookmarkDto> SortBookmarks(
|
||||
this IEnumerable<BookmarkDto> bookmarks) {
|
||||
return bookmarks
|
||||
.OrderBy(b => b.SortOrder)
|
||||
.ThenBy(b => b.BookmarkId);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue