mirror of
https://github.com/Kareadita/Kavita.git
synced 2026-06-04 22:05:21 -04:00
Massive UI Cleanup (#4466)
Co-authored-by: KindlyFire <10267586+kindlyfire@users.noreply.github.com> Co-authored-by: Hosted Weblate <hosted@weblate.org> Co-authored-by: Adam Havránek <adamhavra@seznam.cz> Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com> Co-authored-by: Alexey <lewadedun@gmail.com> Co-authored-by: Anon Bitardov <timurvolga23+weblate@gmail.com> Co-authored-by: Ferran <ferrancette@gmail.com> Co-authored-by: Gneb <goozi12345@gmail.com> Co-authored-by: Robin Stolpe <robinstolpe@slashmad.com> Co-authored-by: 안세훈 <on9686@gmail.com> Co-authored-by: Tijl Van den Brugghen <contact@tijlvdb.me>
This commit is contained in:
@@ -66,10 +66,12 @@ public class CollectionController : BaseApiController
|
||||
/// <param name="collectionId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("single")]
|
||||
public async Task<ActionResult<IEnumerable<AppUserCollectionDto>>> GetTag(int collectionId)
|
||||
public async Task<ActionResult<AppUserCollectionDto>> GetTag(int collectionId)
|
||||
{
|
||||
var collections = await _unitOfWork.CollectionTagRepository.GetCollectionDtosAsync(UserId, false);
|
||||
return Ok(collections.FirstOrDefault(c => c.Id == collectionId));
|
||||
var result = await _unitOfWork.CollectionTagRepository.GetCollectionDtoAsync(collectionId, UserId);
|
||||
if (result == null) return NotFound(); // TODO: Figure out how to best handle restrictions/not found across the codebase
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -101,10 +103,10 @@ public class CollectionController : BaseApiController
|
||||
/// <remarks>UI does not contain controls to update title</remarks>
|
||||
/// </summary>
|
||||
/// <param name="updatedTag"></param>
|
||||
/// <returns></returns>
|
||||
/// <returns>The updated tag entity</returns>
|
||||
[HttpPost("update")]
|
||||
[DisallowRole(PolicyConstants.ReadOnlyRole)]
|
||||
public async Task<ActionResult> UpdateTag(AppUserCollectionDto updatedTag)
|
||||
public async Task<ActionResult<AppUserCollectionDto>> UpdateTag(AppUserCollectionDto updatedTag)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -112,7 +114,7 @@ public class CollectionController : BaseApiController
|
||||
{
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CollectionUpdated,
|
||||
MessageFactory.CollectionUpdatedEvent(updatedTag.Id), false);
|
||||
return Ok(await _localizationService.Translate(UserId, "collection-updated-successfully"));
|
||||
return Ok(await _unitOfWork.CollectionTagRepository.GetCollectionDtoAsync(updatedTag.Id, UserId));
|
||||
}
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
|
||||
@@ -24,6 +24,7 @@ using EasyCaching.Core;
|
||||
using Hangfire;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -68,10 +69,10 @@ public class LibraryController : BaseApiController
|
||||
/// Creates a new Library. Upon library creation, adds new library to all Admin accounts.
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
/// <returns>Created Library</returns>
|
||||
[Authorize(Policy = PolicyGroups.AdminPolicy)]
|
||||
[HttpPost("create")]
|
||||
public async Task<ActionResult> AddLibrary(UpdateLibraryDto dto)
|
||||
public async Task<ActionResult<LibraryDto?>> AddLibrary(UpdateLibraryDto dto)
|
||||
{
|
||||
if (await _unitOfWork.LibraryRepository.LibraryExists(dto.Name))
|
||||
{
|
||||
@@ -161,7 +162,7 @@ public class LibraryController : BaseApiController
|
||||
await _eventHub.SendMessageAsync(MessageFactory.SideNavUpdate,
|
||||
MessageFactory.SideNavUpdateEvent(UserId), false);
|
||||
|
||||
return Ok();
|
||||
return Ok(await _unitOfWork.LibraryRepository.GetLibraryDtoByIdAsync(library.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,17 +208,18 @@ public class LibraryController : BaseApiController
|
||||
/// <summary>
|
||||
/// Return a specific library
|
||||
/// </summary>
|
||||
/// <remarks>If the user is not an admin, only id, type, and name will be returned</remarks>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy = PolicyGroups.AdminPolicy)]
|
||||
[ProducesResponseType<LibraryDto>(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType<LiteLibraryDto>(StatusCodes.Status200OK)]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<LibraryDto?>> GetLibrary(int libraryId)
|
||||
{
|
||||
var username = Username!;
|
||||
if (string.IsNullOrEmpty(username)) return Unauthorized();
|
||||
|
||||
var libraries = await GetLibrariesForUser(username);
|
||||
|
||||
return Ok(libraries.FirstOrDefault(l => l.Id == libraryId));
|
||||
if (User.IsInRole(PolicyConstants.AdminRole))
|
||||
{
|
||||
return Ok(await _unitOfWork.LibraryRepository.GetLibraryDtoByIdAsync(libraryId));
|
||||
}
|
||||
return Ok(await _unitOfWork.LibraryRepository.GetLiteLibraryDtoByIdAsync(libraryId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -227,10 +229,7 @@ public class LibraryController : BaseApiController
|
||||
[HttpGet("libraries")]
|
||||
public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibraries()
|
||||
{
|
||||
var username = Username!;
|
||||
if (string.IsNullOrEmpty(username)) return Unauthorized();
|
||||
|
||||
return Ok(await GetLibrariesForUser(username));
|
||||
return Ok(await GetLibrariesForUser(Username!));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -674,7 +673,7 @@ public class LibraryController : BaseApiController
|
||||
|
||||
await _libraryCacheProvider.RemoveByPrefixAsync(CacheKey);
|
||||
|
||||
return Ok();
|
||||
return Ok(await _unitOfWork.LibraryRepository.GetLibraryDtoByIdAsync(library.Id));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -159,9 +159,9 @@ public class SeriesController : BaseApiController
|
||||
/// Updates the Series
|
||||
/// </summary>
|
||||
/// <param name="updateSeries"></param>
|
||||
/// <returns></returns>
|
||||
/// <returns>Updated Series</returns>
|
||||
[HttpPost("update")]
|
||||
public async Task<ActionResult> UpdateSeries(UpdateSeriesDto updateSeries)
|
||||
public async Task<ActionResult<SeriesDto>> UpdateSeries(UpdateSeriesDto updateSeries)
|
||||
{
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(updateSeries.Id);
|
||||
if (series == null)
|
||||
@@ -206,7 +206,7 @@ public class SeriesController : BaseApiController
|
||||
await _taskScheduler.RefreshSeriesMetadata(series.LibraryId, series.Id);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(series.Id, UserId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -233,7 +233,7 @@ public class SeriesController : BaseApiController
|
||||
/// <param name="userParams">Page size and offset</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("recently-updated-series")]
|
||||
public async Task<ActionResult<IList<RecentlyAddedItemDto>>> GetRecentlyAddedChapters([FromQuery] UserParams? userParams)
|
||||
public async Task<ActionResult<IList<GroupedSeriesDto>>> GetRecentlyAddedChapters([FromQuery] UserParams? userParams)
|
||||
{
|
||||
userParams ??= UserParams.Default;
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyUpdatedSeries(UserId, userParams));
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.DTOs.Dashboard;
|
||||
|
||||
/// <summary>
|
||||
/// A mesh of data for Recently added volume/chapters
|
||||
/// </summary>
|
||||
public sealed record RecentlyAddedItemDto
|
||||
{
|
||||
public string SeriesName { get; set; } = default!;
|
||||
public int SeriesId { get; set; }
|
||||
public int LibraryId { get; set; }
|
||||
public LibraryType LibraryType { get; set; }
|
||||
/// <summary>
|
||||
/// This will automatically map to Volume X, Chapter Y, etc.
|
||||
/// </summary>
|
||||
public string Title { get; set; } = default!;
|
||||
public DateTime Created { get; set; }
|
||||
/// <summary>
|
||||
/// Chapter Id if this is a chapter. Not guaranteed to be set.
|
||||
/// </summary>
|
||||
public int ChapterId { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Volume Id if this is a chapter. Not guaranteed to be set.
|
||||
/// </summary>
|
||||
public int VolumeId { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// This is used only on the UI. It is just index of being added.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
public MangaFormat Format { get; set; }
|
||||
|
||||
}
|
||||
@@ -6,15 +6,22 @@ using API.Entities.Enums;
|
||||
namespace API.DTOs;
|
||||
#nullable enable
|
||||
|
||||
public sealed record LibraryDto
|
||||
/// <summary>
|
||||
/// This is a LibraryDto that non-admins can resolve that has the core information they need
|
||||
/// </summary>
|
||||
public record LiteLibraryDto
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string? Name { get; init; }
|
||||
public LibraryType Type { get; init; }
|
||||
}
|
||||
|
||||
public sealed record LibraryDto : LiteLibraryDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Last time Library was scanned
|
||||
/// </summary>
|
||||
public DateTime LastScanned { get; init; }
|
||||
public LibraryType Type { get; init; }
|
||||
/// <summary>
|
||||
/// An optional Cover Image or null
|
||||
/// </summary>
|
||||
|
||||
@@ -231,7 +231,7 @@ public class AutoMapperProfiles : Profile
|
||||
.ForMember(dest => dest.LibraryName,
|
||||
opt => opt.MapFrom(src => src.Library.Name));
|
||||
|
||||
|
||||
CreateMap<Library, LiteLibraryDto>();
|
||||
CreateMap<Library, LibraryDto>()
|
||||
.ForMember(dest => dest.Folders,
|
||||
opt =>
|
||||
|
||||
@@ -50,6 +50,11 @@ public interface ICollectionTagRepository
|
||||
/// <param name="includePromoted"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<AppUserCollectionDto>> GetCollectionDtosAsync(int userId, bool includePromoted = false);
|
||||
/// <summary>
|
||||
/// Returns the collection if the user owns it or the collection is promoted
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<AppUserCollectionDto?> GetCollectionDtoAsync(int collectionId, int userId);
|
||||
Task<PagedList<AppUserCollectionDto>> GetCollectionDtosPagedAsync(int userId, UserParams userParams, bool includePromoted = false);
|
||||
Task<IEnumerable<AppUserCollectionDto>> GetCollectionDtosBySeriesAsync(int userId, int seriesId, bool includePromoted = false);
|
||||
|
||||
@@ -108,6 +113,17 @@ public class CollectionTagRepository : ICollectionTagRepository
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<AppUserCollectionDto?> GetCollectionDtoAsync(int collectionId, int userId)
|
||||
{
|
||||
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||
return await _context.AppUserCollection
|
||||
.Where(uc => (uc.AppUserId == userId || uc.Promoted) && uc.Id == collectionId)
|
||||
.WhereIf(ageRating.AgeRating != AgeRating.NotApplicable, uc => uc.AgeRating <= ageRating.AgeRating)
|
||||
.OrderBy(uc => uc.Title)
|
||||
.ProjectTo<AppUserCollectionDto>(_mapper.ConfigurationProvider)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AppUserCollectionDto>> GetCollectionDtosAsync(int userId, bool includePromoted = false)
|
||||
{
|
||||
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||
|
||||
@@ -37,6 +37,8 @@ public interface ILibraryRepository
|
||||
void Update(Library library);
|
||||
void Delete(Library? library);
|
||||
Task<IEnumerable<LibraryDto>> GetLibraryDtosAsync();
|
||||
Task<LibraryDto?> GetLibraryDtoByIdAsync(int libraryId);
|
||||
Task<LiteLibraryDto?> GetLiteLibraryDtoByIdAsync(int libraryId);
|
||||
Task<bool> LibraryExists(string libraryName);
|
||||
Task<Library?> GetLibraryForIdAsync(int libraryId, LibraryIncludes includes = LibraryIncludes.None);
|
||||
Task<IList<LibraryDto>> GetLibraryDtosForUsernameAsync(string userName);
|
||||
@@ -214,6 +216,23 @@ public class LibraryRepository : ILibraryRepository
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<LibraryDto?> GetLibraryDtoByIdAsync(int libraryId)
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(f => f.Folders)
|
||||
.Include(l => l.LibraryFileTypes)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.AsSplitQuery()
|
||||
.FirstOrDefaultAsync(l => l.Id == libraryId);
|
||||
}
|
||||
|
||||
public async Task<LiteLibraryDto?> GetLiteLibraryDtoByIdAsync(int libraryId)
|
||||
{
|
||||
return await _context.Library
|
||||
.ProjectTo<LiteLibraryDto>(_mapper.ConfigurationProvider)
|
||||
.FirstOrDefaultAsync(l => l.Id == libraryId);
|
||||
}
|
||||
|
||||
public async Task<Library?> GetLibraryForIdAsync(int libraryId, LibraryIncludes includes = LibraryIncludes.None)
|
||||
{
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public static class SeriesSort
|
||||
SortField.TimeToRead => query.DoOrderBy(s => s.AvgHoursToRead, sortOptions),
|
||||
SortField.ReleaseYear => query.DoOrderBy(s => s.Metadata.ReleaseYear, sortOptions),
|
||||
SortField.ReadProgress => query.DoOrderBy(s => s.Progress.Where(p => p.SeriesId == s.Id && p.AppUserId == userId)
|
||||
.Select(p => p.LastModified) // TODO: Migrate this to UTC
|
||||
.Select(p => p.LastModifiedUtc)
|
||||
.Max(), sortOptions),
|
||||
SortField.AverageRating => query.DoOrderBy(s => s.ExternalSeriesMetadata.ExternalRatings
|
||||
.Where(p => p.SeriesId == s.Id).Average(p => p.AverageScore), sortOptions),
|
||||
|
||||
+5
-1
@@ -61,5 +61,9 @@
|
||||
"reading-list-item-delete": "No s'han pogut suprimir els elements",
|
||||
"generic-reading-list-create": "S'ha produït un problema en crear la llista de lectura",
|
||||
"generic-reading-list-update": "S'ha produït un problema en actualitzar la llista de lectura",
|
||||
"generic-create-temp-archive": "S'ha produït un problema en crear l'arxiu temporal"
|
||||
"generic-create-temp-archive": "S'ha produït un problema en crear l'arxiu temporal",
|
||||
"locked-out": "S'ha bloquejat l'accés degut a masses intents d'autentificació. Si us plau, espera 10 minuts.",
|
||||
"disabled-account": "El seu compte s'ha deshabilitat. Contacti amb l'administrador.",
|
||||
"register-user": "Hi ha hagut un error al registrar-se",
|
||||
"validate-email": "Hi ha hagut un error al validar l'e-mail: {0}"
|
||||
}
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@
|
||||
"bookmark-dir-permissions": "Níl na ceadanna cearta ag an Eolaire Leabharmharcanna chun Kavita a úsáid",
|
||||
"total-backups": "Caithfidh Cúltaca Iomlána a bheith idir 1 agus 30",
|
||||
"total-logs": "Caithfidh an Logchomhaid Iomlán a bheith idir 1 agus 30",
|
||||
"url-not-valid": "Ní sheolann URL íomhá bhailí ar ais nó éilíonn sé údarú",
|
||||
"url-not-valid": "Ní thugann an URL íomhá bhailí ar ais nó teastaíonn údarú uaidh",
|
||||
"url-required": "Caithfidh tú pas a fháil i url le húsáid",
|
||||
"generic-cover-series-save": "Ní féidir íomhá an chlúdaigh a shábháil sa tSraith",
|
||||
"generic-cover-collection-save": "Ní féidir íomhá an chlúdaigh a shábháil sa Bhailiúchán",
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"confirm-email": "Сначала Вы должны подтвердить свой адрес электронной почты",
|
||||
"confirm-email": "Вы должны подтвердить адрес своей электронной почты",
|
||||
"generate-token": "Возникла проблема с генерацией токена подтверждения электронной почты. Смотрите журналы",
|
||||
"invalid-password": "Неверный пароль",
|
||||
"invalid-email-confirmation": "Неверное подтверждение электронной почты",
|
||||
@@ -9,7 +9,7 @@
|
||||
"email-sent": "Электронное письмо отправлено",
|
||||
"generic-password-update": "В процессе подтверждения нового пароля возникла непредвиденная ошибка",
|
||||
"user-already-confirmed": "Пользователь уже подтвержден",
|
||||
"user-migration-needed": "Для этой учётной записи требуется миграция. Попросите пользователя повторно авторизоваться для запуска процесса переноса",
|
||||
"user-migration-needed": "Для этой учётной записи требуется перенос. Попросите пользователя повторно авторизоваться для запуска процесса переноса",
|
||||
"generic-user-update": "При обновлении пользователя возникало исключение",
|
||||
"disabled-account": "Ваша учетная запись отключена. Обратитесь к администратору сервера.",
|
||||
"locked-out": "Доступ временно заблокирован из-за превышения числа попыток входа. Пожалуйста, подождите 10 минут.",
|
||||
@@ -57,7 +57,7 @@
|
||||
"generic-reading-list-create": "Возникла проблема с созданием читательского списка",
|
||||
"no-cover-image": "Нет изображения обложки",
|
||||
"collection-updated": "Коллекция успешно обновлена",
|
||||
"critical-email-migration": "Возникла проблема в процессе смены электронной почты. Обратитесь в службу поддержки",
|
||||
"critical-email-migration": "Возникла проблема в процессе переноса электронной почты. Обратитесь в службу поддержки",
|
||||
"cache-file-find": "Не удалось найти кешированное изображение. Перезагрузите страницу и попробуйте снова.",
|
||||
"duplicate-bookmark": "Закладка с такими параметрами уже существует",
|
||||
"collection-tag-duplicate": "Коллекция с таким именем уже существует",
|
||||
|
||||
+4
-1
@@ -220,5 +220,8 @@
|
||||
"genre-doesnt-exist": "Žáner neexistuje",
|
||||
"font-url-not-allowed": "Nahrávanie písma pomocou adresy URL je povolené iba z Google Fonts",
|
||||
"annotation-export-failed": "Nepodarilo sa exportovať anotácie, skontrolujte protokoly",
|
||||
"download-not-allowed": "Používateľ nemá povolenia na sťahovanie"
|
||||
"download-not-allowed": "Používateľ nemá povolenia na sťahovanie",
|
||||
"client-device-doesnt-exist": "Klientske zariadenie neexistuje",
|
||||
"auth-key-unique": "Názov autorizačného kľúča musí byť jedinečný pre váš účet",
|
||||
"role-restricted": "Prístup odmietnutý: Vaša rola nemá povolenie na túto akciu"
|
||||
}
|
||||
|
||||
+3
-1
@@ -208,5 +208,7 @@
|
||||
"smart-filter-name-required": "Smart Filter namn krävs",
|
||||
"sidenav-stream-only-delete-smart-filter": "Bara smarta-filter-strömmar kan tas bort från sidomenyn",
|
||||
"smart-filter-system-name": "Du kan inte använda namnet från en ström som systemet tillhandahåller",
|
||||
"password-authentication-disabled": "Lösenords autentisering har stängts av, logga in via OpenID Connect"
|
||||
"password-authentication-disabled": "Lösenords autentisering har stängts av, logga in via OpenID Connect",
|
||||
"oidc-managed": "Användare som hanteras av OIDC kan inte redigeras.",
|
||||
"cannot-change-identity-provider-original-user": "Identitetsleverantören för det ursprungliga administratörskontot kan inte ändras"
|
||||
}
|
||||
|
||||
+3
-3
@@ -172,10 +172,10 @@ public class Program
|
||||
private static void HandleFirstRunConfiguration()
|
||||
{
|
||||
var firstRunConfigFilePath = Path.Join(Directory.GetCurrentDirectory(), "config/appsettings-init.json");
|
||||
if (File.Exists(firstRunConfigFilePath) &&
|
||||
!File.Exists(Path.Join(Directory.GetCurrentDirectory(), "config/appsettings.json")))
|
||||
var actualRunConfigFilePath = Path.Join(Directory.GetCurrentDirectory(), "config/appsettings.json");
|
||||
if (File.Exists(firstRunConfigFilePath) && !File.Exists(actualRunConfigFilePath))
|
||||
{
|
||||
File.Move(firstRunConfigFilePath, Path.Join(Directory.GetCurrentDirectory(), "config/appsettings.json"));
|
||||
File.Move(firstRunConfigFilePath, actualRunConfigFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ public class LocalizationService : ILocalizationService
|
||||
try
|
||||
{
|
||||
var cultureInfo = new System.Globalization.CultureInfo(fileName.Replace('_', '-'));
|
||||
return cultureInfo.NativeName;
|
||||
return cultureInfo.EnglishName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user