mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-02 21:24:18 -04:00
Invite & Library Edit - Missing SideNav Code (#2322)
This commit is contained in:
parent
7c8fdd9ea8
commit
a3afa04be4
@ -459,7 +459,7 @@ public class AccountController : BaseApiController
|
|||||||
if (adminUser == null) return Unauthorized();
|
if (adminUser == null) return Unauthorized();
|
||||||
if (!await _unitOfWork.UserRepository.IsUserAdminAsync(adminUser)) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
if (!await _unitOfWork.UserRepository.IsUserAdminAsync(adminUser)) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(dto.UserId);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(dto.UserId, AppUserIncludes.SideNavStreams);
|
||||||
if (user == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-user"));
|
if (user == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-user"));
|
||||||
|
|
||||||
// Check if username is changing
|
// Check if username is changing
|
||||||
@ -509,6 +509,7 @@ public class AccountController : BaseApiController
|
|||||||
{
|
{
|
||||||
lib.AppUsers ??= new List<AppUser>();
|
lib.AppUsers ??= new List<AppUser>();
|
||||||
lib.AppUsers.Remove(user);
|
lib.AppUsers.Remove(user);
|
||||||
|
user.RemoveSideNavFromLibrary(lib);
|
||||||
}
|
}
|
||||||
|
|
||||||
libraries = (await _unitOfWork.LibraryRepository.GetLibraryForIdsAsync(dto.Libraries, LibraryIncludes.AppUser)).ToList();
|
libraries = (await _unitOfWork.LibraryRepository.GetLibraryForIdsAsync(dto.Libraries, LibraryIncludes.AppUser)).ToList();
|
||||||
@ -518,6 +519,7 @@ public class AccountController : BaseApiController
|
|||||||
{
|
{
|
||||||
lib.AppUsers ??= new List<AppUser>();
|
lib.AppUsers ??= new List<AppUser>();
|
||||||
lib.AppUsers.Add(user);
|
lib.AppUsers.Add(user);
|
||||||
|
user.CreateSideNavFromLibrary(lib);
|
||||||
}
|
}
|
||||||
|
|
||||||
user.AgeRestriction = hasAdminRole ? AgeRating.NotApplicable : dto.AgeRestriction.AgeRating;
|
user.AgeRestriction = hasAdminRole ? AgeRating.NotApplicable : dto.AgeRestriction.AgeRating;
|
||||||
@ -528,6 +530,9 @@ public class AccountController : BaseApiController
|
|||||||
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
|
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
|
||||||
{
|
{
|
||||||
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName), user.Id);
|
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName), user.Id);
|
||||||
|
await _eventHub.SendMessageToAsync(MessageFactory.SideNavUpdate, MessageFactory.SideNavUpdateEvent(user.Id), user.Id);
|
||||||
|
// If we adjust library access, dashboards should re-render
|
||||||
|
await _eventHub.SendMessageToAsync(MessageFactory.DashboardUpdate, MessageFactory.DashboardUpdateEvent(user.Id), user.Id);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -627,8 +632,10 @@ public class AccountController : BaseApiController
|
|||||||
{
|
{
|
||||||
lib.AppUsers ??= new List<AppUser>();
|
lib.AppUsers ??= new List<AppUser>();
|
||||||
lib.AppUsers.Add(user);
|
lib.AppUsers.Add(user);
|
||||||
|
user.CreateSideNavFromLibrary(lib);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_unitOfWork.UserRepository.Update(user);
|
||||||
user.AgeRestriction = hasAdminRole ? AgeRating.NotApplicable : dto.AgeRestriction.AgeRating;
|
user.AgeRestriction = hasAdminRole ? AgeRating.NotApplicable : dto.AgeRestriction.AgeRating;
|
||||||
user.AgeRestrictionIncludeUnknowns = hasAdminRole || dto.AgeRestriction.IncludeUnknowns;
|
user.AgeRestrictionIncludeUnknowns = hasAdminRole || dto.AgeRestriction.IncludeUnknowns;
|
||||||
|
|
||||||
@ -649,6 +656,7 @@ public class AccountController : BaseApiController
|
|||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var emailLink = await _accountService.GenerateEmailLink(Request, user.ConfirmationToken, "confirm-email", dto.Email);
|
var emailLink = await _accountService.GenerateEmailLink(Request, user.ConfirmationToken, "confirm-email", dto.Email);
|
||||||
|
@ -109,16 +109,7 @@ public class LibraryController : BaseApiController
|
|||||||
|
|
||||||
foreach (var user in userNeedingNewLibrary)
|
foreach (var user in userNeedingNewLibrary)
|
||||||
{
|
{
|
||||||
var maxCount = user.SideNavStreams.Select(s => s.Order).Max();
|
user.CreateSideNavFromLibrary(library);
|
||||||
user.SideNavStreams.Add(new AppUserSideNavStream()
|
|
||||||
{
|
|
||||||
Name = library.Name,
|
|
||||||
Order = maxCount + 1,
|
|
||||||
IsProvided = false,
|
|
||||||
StreamType = SideNavStreamType.Library,
|
|
||||||
LibraryId = library.Id,
|
|
||||||
Visible = true,
|
|
||||||
});
|
|
||||||
_unitOfWork.UserRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +192,7 @@ public class LibraryController : BaseApiController
|
|||||||
[HttpPost("grant-access")]
|
[HttpPost("grant-access")]
|
||||||
public async Task<ActionResult<MemberDto>> UpdateUserLibraries(UpdateLibraryForUserDto updateLibraryForUserDto)
|
public async Task<ActionResult<MemberDto>> UpdateUserLibraries(UpdateLibraryForUserDto updateLibraryForUserDto)
|
||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(updateLibraryForUserDto.Username);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(updateLibraryForUserDto.Username, AppUserIncludes.SideNavStreams);
|
||||||
if (user == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-doesnt-exist"));
|
if (user == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-doesnt-exist"));
|
||||||
|
|
||||||
var libraryString = string.Join(',', updateLibraryForUserDto.SelectedLibraries.Select(x => x.Name));
|
var libraryString = string.Join(',', updateLibraryForUserDto.SelectedLibraries.Select(x => x.Name));
|
||||||
@ -217,10 +208,12 @@ public class LibraryController : BaseApiController
|
|||||||
{
|
{
|
||||||
// Remove
|
// Remove
|
||||||
library.AppUsers.Remove(user);
|
library.AppUsers.Remove(user);
|
||||||
|
user.RemoveSideNavFromLibrary(library);
|
||||||
}
|
}
|
||||||
else if (!libraryContainsUser && libraryIsSelected)
|
else if (!libraryContainsUser && libraryIsSelected)
|
||||||
{
|
{
|
||||||
library.AppUsers.Add(user);
|
library.AppUsers.Add(user);
|
||||||
|
user.CreateSideNavFromLibrary(library);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,6 +228,11 @@ public class LibraryController : BaseApiController
|
|||||||
_logger.LogInformation("Added: {SelectedLibraries} to {Username}",libraryString, updateLibraryForUserDto.Username);
|
_logger.LogInformation("Added: {SelectedLibraries} to {Username}",libraryString, updateLibraryForUserDto.Username);
|
||||||
// Bust cache
|
// Bust cache
|
||||||
await _libraryCacheProvider.RemoveByPrefixAsync(CacheKey);
|
await _libraryCacheProvider.RemoveByPrefixAsync(CacheKey);
|
||||||
|
|
||||||
|
// TODO: Update a user's SideNav based on library access
|
||||||
|
|
||||||
|
_unitOfWork.UserRepository.Update(user);
|
||||||
|
|
||||||
return Ok(_mapper.Map<MemberDto>(user));
|
return Ok(_mapper.Map<MemberDto>(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,6 +929,12 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
query ??= _context.Series
|
query ??= _context.Series
|
||||||
.AsNoTracking();
|
.AsNoTracking();
|
||||||
|
|
||||||
|
// When the user has no access, just return instantly
|
||||||
|
if (userLibraries.Count == 0)
|
||||||
|
{
|
||||||
|
return query.Where(s => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// First setup any FilterField.Libraries in the statements, as these don't have any traditional query statements applied here
|
// First setup any FilterField.Libraries in the statements, as these don't have any traditional query statements applied here
|
||||||
|
46
API/Extensions/AppUserExtensions.cs
Normal file
46
API/Extensions/AppUserExtensions.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using API.Entities;
|
||||||
|
using API.Helpers;
|
||||||
|
|
||||||
|
namespace API.Extensions;
|
||||||
|
|
||||||
|
public static class AppUserExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new SideNavStream to the user's SideNavStreams. This user should have these streams already loaded
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="library"></param>
|
||||||
|
public static void CreateSideNavFromLibrary(this AppUser user, Library library)
|
||||||
|
{
|
||||||
|
user.SideNavStreams ??= new List<AppUserSideNavStream>();
|
||||||
|
var maxCount = user.SideNavStreams.Select(s => s.Order).DefaultIfEmpty().Max();
|
||||||
|
|
||||||
|
if (user.SideNavStreams.FirstOrDefault(s => s.LibraryId == library.Id) != null) return;
|
||||||
|
|
||||||
|
user.SideNavStreams.Add(new AppUserSideNavStream()
|
||||||
|
{
|
||||||
|
Name = library.Name,
|
||||||
|
Order = maxCount + 1,
|
||||||
|
IsProvided = false,
|
||||||
|
StreamType = SideNavStreamType.Library,
|
||||||
|
LibraryId = library.Id,
|
||||||
|
Visible = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void RemoveSideNavFromLibrary(this AppUser user, Library library)
|
||||||
|
{
|
||||||
|
user.SideNavStreams ??= new List<AppUserSideNavStream>();
|
||||||
|
|
||||||
|
// Find the library and remove it
|
||||||
|
var item = user.SideNavStreams.FirstOrDefault(s => s.LibraryId == library.Id);
|
||||||
|
if (item == null) return;
|
||||||
|
user.SideNavStreams.Remove(item);
|
||||||
|
|
||||||
|
OrderableHelper.ReorderItems(user.SideNavStreams);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
60
API/Helpers/OrderableHelper.cs
Normal file
60
API/Helpers/OrderableHelper.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using API.Entities;
|
||||||
|
|
||||||
|
namespace API.Helpers;
|
||||||
|
|
||||||
|
public static class OrderableHelper
|
||||||
|
{
|
||||||
|
public static void ReorderItems(List<AppUserDashboardStream> items, int itemId, int toPosition)
|
||||||
|
{
|
||||||
|
var item = items.Find(r => r.Id == itemId);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
items.Remove(item);
|
||||||
|
items.Insert(toPosition, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
items[i].Order = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReorderItems(List<AppUserSideNavStream> items, int itemId, int toPosition)
|
||||||
|
{
|
||||||
|
var item = items.Find(r => r.Id == itemId);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
items.Remove(item);
|
||||||
|
items.Insert(toPosition, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
items[i].Order = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReorderItems(IList<AppUserSideNavStream> items)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
items[i].Order = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReorderItems(List<ReadingListItem> items, int readingListItemId, int toPosition)
|
||||||
|
{
|
||||||
|
var item = items.Find(r => r.Id == readingListItemId);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
items.Remove(item);
|
||||||
|
items.Insert(toPosition, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
items[i].Order = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -236,28 +236,13 @@ public class ReadingListService : IReadingListService
|
|||||||
public async Task<bool> UpdateReadingListItemPosition(UpdateReadingListPosition dto)
|
public async Task<bool> UpdateReadingListItemPosition(UpdateReadingListPosition dto)
|
||||||
{
|
{
|
||||||
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(dto.ReadingListId)).ToList();
|
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(dto.ReadingListId)).ToList();
|
||||||
ReorderItems(items, dto.ReadingListItemId, dto.ToPosition);
|
OrderableHelper.ReorderItems(items, dto.ReadingListItemId, dto.ToPosition);
|
||||||
|
|
||||||
if (!_unitOfWork.HasChanges()) return true;
|
if (!_unitOfWork.HasChanges()) return true;
|
||||||
|
|
||||||
return await _unitOfWork.CommitAsync();
|
return await _unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReorderItems(List<ReadingListItem> items, int readingListItemId, int toPosition)
|
|
||||||
{
|
|
||||||
var item = items.Find(r => r.Id == readingListItemId);
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
items.Remove(item);
|
|
||||||
items.Insert(toPosition, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < items.Count; i++)
|
|
||||||
{
|
|
||||||
items[i].Order = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes a certain reading list item from a reading list
|
/// Removes a certain reading list item from a reading list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -477,7 +462,7 @@ public class ReadingListService : IReadingListService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReorderItems(items, readingListItem.Id, order);
|
OrderableHelper.ReorderItems(items, readingListItem.Id, order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using API.DTOs.Dashboard;
|
|||||||
using API.DTOs.SideNav;
|
using API.DTOs.SideNav;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
using API.Helpers;
|
||||||
using API.SignalR;
|
using API.SignalR;
|
||||||
using Kavita.Common;
|
using Kavita.Common;
|
||||||
using Kavita.Common.Helpers;
|
using Kavita.Common.Helpers;
|
||||||
@ -127,7 +128,7 @@ public class StreamService : IStreamService
|
|||||||
if (stream.Order == dto.ToPosition) return;
|
if (stream.Order == dto.ToPosition) return;
|
||||||
|
|
||||||
var list = user!.DashboardStreams.ToList();
|
var list = user!.DashboardStreams.ToList();
|
||||||
ReorderItems(list, stream.Id, dto.ToPosition);
|
OrderableHelper.ReorderItems(list, stream.Id, dto.ToPosition);
|
||||||
user.DashboardStreams = list;
|
user.DashboardStreams = list;
|
||||||
|
|
||||||
_unitOfWork.UserRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
@ -263,7 +264,7 @@ public class StreamService : IStreamService
|
|||||||
if (stream.Order == dto.ToPosition) return;
|
if (stream.Order == dto.ToPosition) return;
|
||||||
|
|
||||||
var list = user!.SideNavStreams.ToList();
|
var list = user!.SideNavStreams.ToList();
|
||||||
ReorderItems(list, stream.Id, dto.ToPosition);
|
OrderableHelper.ReorderItems(list, stream.Id, dto.ToPosition);
|
||||||
user.SideNavStreams = list;
|
user.SideNavStreams = list;
|
||||||
_unitOfWork.UserRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
|
|
||||||
@ -340,33 +341,5 @@ public class StreamService : IStreamService
|
|||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReorderItems(List<AppUserDashboardStream> items, int itemId, int toPosition)
|
|
||||||
{
|
|
||||||
var item = items.Find(r => r.Id == itemId);
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
items.Remove(item);
|
|
||||||
items.Insert(toPosition, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < items.Count; i++)
|
|
||||||
{
|
|
||||||
items[i].Order = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ReorderItems(List<AppUserSideNavStream> items, int itemId, int toPosition)
|
|
||||||
{
|
|
||||||
var item = items.Find(r => r.Id == itemId);
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
items.Remove(item);
|
|
||||||
items.Insert(toPosition, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < items.Count; i++)
|
|
||||||
{
|
|
||||||
items[i].Order = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
|
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
|
||||||
import {ActivatedRoute, RouterLink} from '@angular/router';
|
import {ActivatedRoute, RouterLink} from '@angular/router';
|
||||||
import {ToastrService} from 'ngx-toastr';
|
|
||||||
import {ServerService} from 'src/app/_services/server.service';
|
|
||||||
import {Title} from '@angular/platform-browser';
|
import {Title} from '@angular/platform-browser';
|
||||||
import {NavService} from '../../_services/nav.service';
|
import {NavService} from '../../_services/nav.service';
|
||||||
import {SentenceCasePipe} from '../../pipe/sentence-case.pipe';
|
import {SentenceCasePipe} from '../../pipe/sentence-case.pipe';
|
||||||
@ -20,7 +18,7 @@ import {NgbNav, NgbNavContent, NgbNavItem, NgbNavItemRole, NgbNavLink, NgbNavOut
|
|||||||
import {
|
import {
|
||||||
SideNavCompanionBarComponent
|
SideNavCompanionBarComponent
|
||||||
} from '../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
|
} from '../../sidenav/_components/side-nav-companion-bar/side-nav-companion-bar.component';
|
||||||
import {TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
import {translate, TranslocoDirective, TranslocoService} from "@ngneat/transloco";
|
||||||
|
|
||||||
enum TabID {
|
enum TabID {
|
||||||
General = '',
|
General = '',
|
||||||
@ -66,8 +64,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
return TabID;
|
return TabID;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public route: ActivatedRoute, private serverService: ServerService,
|
constructor(public route: ActivatedRoute, private titleService: Title, public navService: NavService) {
|
||||||
private toastr: ToastrService, private titleService: Title, public navService: NavService) {
|
|
||||||
this.route.fragment.subscribe(frag => {
|
this.route.fragment.subscribe(frag => {
|
||||||
const tab = this.tabs.filter(item => item.fragment === frag);
|
const tab = this.tabs.filter(item => item.fragment === frag);
|
||||||
if (tab.length > 0) {
|
if (tab.length > 0) {
|
||||||
@ -81,6 +78,6 @@ export class DashboardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.titleService.setTitle('Kavita - ' + this.translocoService.translate('admin-dashboard.title'));
|
this.titleService.setTitle('Kavita - ' + translate('admin-dashboard.title'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.8.11"
|
"version": "0.7.8.12"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user