Misc Fixes + Enhancements (#1875)

* Moved Collapse Series with relationships into a user preference rather than library setting.

* Fixed bookmarks not converting to webp after initial save

* Fixed a bug where when merging we'd print out a duplicate series error when we shouldn't have

* Fixed a bug where clicking on a genre or tag from server stats wouldn't load all-series page in a filtered state.

* Implemented the ability to have Login role and thus disable accounts.

* Ensure first time flow gets the Login role

* Refactored user management screen so that pending users can be edited or deleted before the end user accepts the invite. A side effect is old legacy users that were here before email was required can now be deleted.

* Show a progress bar under the main series image on larger viewports to show whole series progress.

* Removed code no longer needed

* Cleanup tags, people, collections without connections after editing series metadata.

* Moved the Entity Builders to the main project
This commit is contained in:
Joe Milazzo 2023-03-10 19:09:38 -06:00 committed by GitHub
parent c62e594792
commit bd19b282d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 2186 additions and 239 deletions

View file

@ -0,0 +1,36 @@
using System.Threading.Tasks;
using API.Constants;
using API.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace API.Data;
/// <summary>
/// Added in v0.7.1.18
/// </summary>
public static class MigrateLoginRoles
{
/// <summary>
/// Will not run if any users have the <see cref="PolicyConstants.LoginRole"/> role already
/// </summary>
/// <param name="unitOfWork"></param>
/// <param name="userManager"></param>
/// <param name="logger"></param>
public static async Task Migrate(IUnitOfWork unitOfWork, UserManager<AppUser> userManager, ILogger<Program> logger)
{
var usersWithRole = await userManager.GetUsersInRoleAsync(PolicyConstants.LoginRole);
if (usersWithRole.Count != 0) return;
logger.LogCritical("Running MigrateLoginRoles migration");
var allUsers = await unitOfWork.UserRepository.GetAllUsersAsync();
foreach (var user in allUsers)
{
await userManager.RemoveFromRoleAsync(user, PolicyConstants.LoginRole);
await userManager.AddToRoleAsync(user, PolicyConstants.LoginRole);
}
logger.LogInformation("MigrateLoginRoles migration complete");
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace API.Data.Migrations
{
/// <inheritdoc />
public partial class MoveCollapseSeriesToUserPref : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "CollapseSeriesRelationships",
table: "AppUserPreferences",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CollapseSeriesRelationships",
table: "AppUserPreferences");
}
}
}

View file

@ -15,7 +15,7 @@ namespace API.Data.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
modelBuilder.HasAnnotation("ProductVersion", "7.0.3");
modelBuilder.Entity("API.Entities.AppRole", b =>
{
@ -221,10 +221,10 @@ namespace API.Data.Migrations
b.Property<int>("BookReaderReadingDirection")
.HasColumnType("INTEGER");
b.Property<int>("BookReaderWritingStyle")
b.Property<bool>("BookReaderTapToPaginate")
.HasColumnType("INTEGER");
b.Property<bool>("BookReaderTapToPaginate")
b.Property<int>("BookReaderWritingStyle")
.HasColumnType("INTEGER");
b.Property<string>("BookThemeName")
@ -232,6 +232,9 @@ namespace API.Data.Migrations
.HasColumnType("TEXT")
.HasDefaultValue("Dark");
b.Property<bool>("CollapseSeriesRelationships")
.HasColumnType("INTEGER");
b.Property<bool>("EmulateBook")
.HasColumnType("INTEGER");
@ -601,9 +604,6 @@ namespace API.Data.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("CollapseSeriesRelationships")
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");

View file

@ -770,9 +770,9 @@ public class SeriesRepository : ISeriesRepository
// NOTE: Why do we even have libraryId when the filter has the actual libraryIds?
var userLibraries = await GetUserLibrariesForFilteredQuery(libraryId, userId, queryContext);
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
var onlyParentSeries = await _context.Library.AsNoTracking()
.Where(l => filter.Libraries.Contains(l.Id))
.AllAsync(l => l.CollapseSeriesRelationships);
var onlyParentSeries = await _context.AppUserPreferences.Where(u => u.AppUserId == userId)
.Select(u => u.CollapseSeriesRelationships)
.SingleOrDefaultAsync();
var formats = ExtractFilters(libraryId, userId, filter, ref userLibraries,
out var allPeopleIds, out var hasPeopleFilter, out var hasGenresFilter,

View file

@ -39,8 +39,7 @@ public interface IUserRepository
void Add(AppUserBookmark bookmark);
public void Delete(AppUser? user);
void Delete(AppUserBookmark bookmark);
Task<IEnumerable<MemberDto>> GetEmailConfirmedMemberDtosAsync();
Task<IEnumerable<MemberDto>> GetPendingMemberDtosAsync();
Task<IEnumerable<MemberDto>> GetEmailConfirmedMemberDtosAsync(bool emailConfirmed = true);
Task<IEnumerable<AppUser>> GetAdminUsersAsync();
Task<bool> IsUserAdminAsync(AppUser? user);
Task<AppUserRating?> GetUserRatingAsync(int seriesId, int userId);
@ -329,10 +328,10 @@ public class UserRepository : IUserRepository
}
public async Task<IEnumerable<MemberDto>> GetEmailConfirmedMemberDtosAsync()
public async Task<IEnumerable<MemberDto>> GetEmailConfirmedMemberDtosAsync(bool emailConfirmed = true)
{
return await _context.Users
.Where(u => u.EmailConfirmed)
.Where(u => (emailConfirmed && u.EmailConfirmed) || !emailConfirmed)
.Include(x => x.Libraries)
.Include(r => r.UserRoles)
.ThenInclude(r => r.Role)
@ -344,45 +343,8 @@ public class UserRepository : IUserRepository
Email = u.Email,
Created = u.Created,
LastActive = u.LastActive,
Roles = u.UserRoles.Select(r => r.Role.Name).ToList()!,
AgeRestriction = new AgeRestrictionDto()
{
AgeRating = u.AgeRestriction,
IncludeUnknowns = u.AgeRestrictionIncludeUnknowns
},
Libraries = u.Libraries.Select(l => new LibraryDto
{
Name = l.Name,
Type = l.Type,
LastScanned = l.LastScanned,
Folders = l.Folders.Select(x => x.Path).ToList()
}).ToList()
})
.AsSplitQuery()
.AsNoTracking()
.ToListAsync();
}
/// <summary>
/// Returns a list of users that are considered Pending by invite. This means email is unconfirmed and they have never logged in
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<MemberDto>> GetPendingMemberDtosAsync()
{
return await _context.Users
.Where(u => !u.EmailConfirmed && u.LastActive == DateTime.MinValue)
.Include(x => x.Libraries)
.Include(r => r.UserRoles)
.ThenInclude(r => r.Role)
.OrderBy(u => u.UserName)
.Select(u => new MemberDto
{
Id = u.Id,
Username = u.UserName,
Email = u.Email,
Created = u.Created,
LastActive = u.LastActive,
Roles = u.UserRoles.Select(r => r.Role.Name).ToList()!,
Roles = u.UserRoles.Select(r => r.Role.Name).ToList(),
IsPending = !u.EmailConfirmed,
AgeRestriction = new AgeRestrictionDto()
{
AgeRating = u.AgeRestriction,