mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-03 13:44:31 -04:00
Read Only Account Changes + Fixes from last PR (#3453)
This commit is contained in:
parent
41c346d5e6
commit
a8144a1d3e
@ -457,6 +457,7 @@ public class AccountController : BaseApiController
|
|||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
if (user == null) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
if (user == null) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
|
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
|
||||||
if (!await _accountService.CanChangeAgeRestriction(user)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
if (!await _accountService.CanChangeAgeRestriction(user)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
@ -494,6 +495,7 @@ public class AccountController : BaseApiController
|
|||||||
var adminUser = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
var adminUser = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
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"));
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(dto.UserId, AppUserIncludes.SideNavStreams);
|
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"));
|
||||||
@ -911,7 +913,6 @@ public class AccountController : BaseApiController
|
|||||||
[EnableRateLimiting("Authentication")]
|
[EnableRateLimiting("Authentication")]
|
||||||
public async Task<ActionResult<string>> ForgotPassword([FromQuery] string email)
|
public async Task<ActionResult<string>> ForgotPassword([FromQuery] string email)
|
||||||
{
|
{
|
||||||
|
|
||||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(email);
|
var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(email);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
@ -1012,6 +1013,8 @@ public class AccountController : BaseApiController
|
|||||||
await _localizationService.Translate(user.Id, "user-migration-needed"));
|
await _localizationService.Translate(user.Id, "user-migration-needed"));
|
||||||
if (user.EmailConfirmed) return BadRequest(await _localizationService.Translate(user.Id, "user-already-confirmed"));
|
if (user.EmailConfirmed) return BadRequest(await _localizationService.Translate(user.Id, "user-already-confirmed"));
|
||||||
|
|
||||||
|
// TODO: If the target user is read only, we might want to just forgo this
|
||||||
|
|
||||||
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||||
user.ConfirmationToken = token;
|
user.ConfirmationToken = token;
|
||||||
_unitOfWork.UserRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Constants;
|
||||||
using API.DTOs.ReadingLists.CBL;
|
using API.DTOs.ReadingLists.CBL;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
@ -20,11 +21,13 @@ public class CblController : BaseApiController
|
|||||||
{
|
{
|
||||||
private readonly IReadingListService _readingListService;
|
private readonly IReadingListService _readingListService;
|
||||||
private readonly IDirectoryService _directoryService;
|
private readonly IDirectoryService _directoryService;
|
||||||
|
private readonly ILocalizationService _localizationService;
|
||||||
|
|
||||||
public CblController(IReadingListService readingListService, IDirectoryService directoryService)
|
public CblController(IReadingListService readingListService, IDirectoryService directoryService, ILocalizationService localizationService)
|
||||||
{
|
{
|
||||||
_readingListService = readingListService;
|
_readingListService = readingListService;
|
||||||
_directoryService = directoryService;
|
_directoryService = directoryService;
|
||||||
|
_localizationService = localizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -91,6 +94,8 @@ public class CblController : BaseApiController
|
|||||||
[SwaggerIgnore]
|
[SwaggerIgnore]
|
||||||
public async Task<ActionResult<CblImportSummaryDto>> ImportCbl(IFormFile cbl, [FromQuery] bool dryRun = false, [FromQuery] bool useComicVineMatching = false)
|
public async Task<ActionResult<CblImportSummaryDto>> ImportCbl(IFormFile cbl, [FromQuery] bool dryRun = false, [FromQuery] bool useComicVineMatching = false)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = User.GetUserId();
|
var userId = User.GetUserId();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Constants;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
@ -58,6 +59,8 @@ public class ChapterController : BaseApiController
|
|||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public async Task<ActionResult<bool>> DeleteChapter(int chapterId)
|
public async Task<ActionResult<bool>> DeleteChapter(int chapterId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
|
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
|
||||||
if (chapter == null)
|
if (chapter == null)
|
||||||
return BadRequest(_localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
|
return BadRequest(_localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
|
||||||
|
@ -105,6 +105,8 @@ public class CollectionController : BaseApiController
|
|||||||
[HttpPost("update")]
|
[HttpPost("update")]
|
||||||
public async Task<ActionResult> UpdateTag(AppUserCollectionDto updatedTag)
|
public async Task<ActionResult> UpdateTag(AppUserCollectionDto updatedTag)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (await _collectionService.UpdateTag(updatedTag, User.GetUserId()))
|
if (await _collectionService.UpdateTag(updatedTag, User.GetUserId()))
|
||||||
@ -130,6 +132,8 @@ public class CollectionController : BaseApiController
|
|||||||
[HttpPost("promote-multiple")]
|
[HttpPost("promote-multiple")]
|
||||||
public async Task<ActionResult> PromoteMultipleCollections(PromoteCollectionsDto dto)
|
public async Task<ActionResult> PromoteMultipleCollections(PromoteCollectionsDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
// This needs to take into account owner as I can select other users cards
|
// This needs to take into account owner as I can select other users cards
|
||||||
var collections = await _unitOfWork.CollectionTagRepository.GetCollectionsByIds(dto.CollectionIds);
|
var collections = await _unitOfWork.CollectionTagRepository.GetCollectionsByIds(dto.CollectionIds);
|
||||||
var userId = User.GetUserId();
|
var userId = User.GetUserId();
|
||||||
@ -161,6 +165,8 @@ public class CollectionController : BaseApiController
|
|||||||
[HttpPost("delete-multiple")]
|
[HttpPost("delete-multiple")]
|
||||||
public async Task<ActionResult> DeleteMultipleCollections(DeleteCollectionsDto dto)
|
public async Task<ActionResult> DeleteMultipleCollections(DeleteCollectionsDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
// This needs to take into account owner as I can select other users cards
|
// This needs to take into account owner as I can select other users cards
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
@ -182,6 +188,8 @@ public class CollectionController : BaseApiController
|
|||||||
[HttpPost("update-for-series")]
|
[HttpPost("update-for-series")]
|
||||||
public async Task<ActionResult> AddToMultipleSeries(CollectionTagBulkAddDto dto)
|
public async Task<ActionResult> AddToMultipleSeries(CollectionTagBulkAddDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
// Create a new tag and save
|
// Create a new tag and save
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
@ -223,6 +231,8 @@ public class CollectionController : BaseApiController
|
|||||||
[HttpPost("update-series")]
|
[HttpPost("update-series")]
|
||||||
public async Task<ActionResult> RemoveTagFromMultipleSeries(UpdateSeriesForTagDto updateSeriesForTagDto)
|
public async Task<ActionResult> RemoveTagFromMultipleSeries(UpdateSeriesForTagDto updateSeriesForTagDto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(updateSeriesForTagDto.Tag.Id, CollectionIncludes.Series);
|
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(updateSeriesForTagDto.Tag.Id, CollectionIncludes.Series);
|
||||||
@ -247,6 +257,8 @@ public class CollectionController : BaseApiController
|
|||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public async Task<ActionResult> DeleteTag(int tagId)
|
public async Task<ActionResult> DeleteTag(int tagId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
|
||||||
@ -276,6 +288,8 @@ public class CollectionController : BaseApiController
|
|||||||
[HttpGet("mal-stacks")]
|
[HttpGet("mal-stacks")]
|
||||||
public async Task<ActionResult<IList<MalStackDto>>> GetMalStacksForUser()
|
public async Task<ActionResult<IList<MalStackDto>>> GetMalStacksForUser()
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
return Ok(await _externalMetadataService.GetStacksForUser(User.GetUserId()));
|
return Ok(await _externalMetadataService.GetStacksForUser(User.GetUserId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +303,8 @@ public class CollectionController : BaseApiController
|
|||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Collections);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
|
|
||||||
// Validation check to ensure stack doesn't exist already
|
// Validation check to ensure stack doesn't exist already
|
||||||
if (await _unitOfWork.CollectionTagRepository.CollectionExists(dto.Title, user.Id))
|
if (await _unitOfWork.CollectionTagRepository.CollectionExists(dto.Title, user.Id))
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Constants;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs.Dashboard;
|
using API.DTOs.Dashboard;
|
||||||
@ -9,6 +10,7 @@ using API.DTOs.Filtering.v2;
|
|||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
|
using API.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace API.Controllers;
|
namespace API.Controllers;
|
||||||
@ -21,10 +23,12 @@ namespace API.Controllers;
|
|||||||
public class FilterController : BaseApiController
|
public class FilterController : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
private readonly ILocalizationService _localizationService;
|
||||||
|
|
||||||
public FilterController(IUnitOfWork unitOfWork)
|
public FilterController(IUnitOfWork unitOfWork, ILocalizationService localizationService)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
|
_localizationService = localizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -37,6 +41,7 @@ public class FilterController : BaseApiController
|
|||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.SmartFilters);
|
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.SmartFilters);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(dto.Name)) return BadRequest("Name must be set");
|
if (string.IsNullOrWhiteSpace(dto.Name)) return BadRequest("Name must be set");
|
||||||
if (Seed.DefaultStreams.Any(s => s.Name.Equals(dto.Name, StringComparison.InvariantCultureIgnoreCase)))
|
if (Seed.DefaultStreams.Any(s => s.Name.Equals(dto.Name, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
@ -78,6 +83,8 @@ public class FilterController : BaseApiController
|
|||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public async Task<ActionResult> DeleteFilter(int filterId)
|
public async Task<ActionResult> DeleteFilter(int filterId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
var filter = await _unitOfWork.AppUserSmartFilterRepository.GetById(filterId);
|
var filter = await _unitOfWork.AppUserSmartFilterRepository.GetById(filterId);
|
||||||
if (filter == null) return Ok();
|
if (filter == null) return Ok();
|
||||||
// This needs to delete any dashboard filters that have it too
|
// This needs to delete any dashboard filters that have it too
|
||||||
|
@ -9,6 +9,7 @@ using API.Services;
|
|||||||
using API.Services.Tasks.Metadata;
|
using API.Services.Tasks.Metadata;
|
||||||
using API.SignalR;
|
using API.SignalR;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Nager.ArticleNumber;
|
using Nager.ArticleNumber;
|
||||||
|
|
||||||
@ -72,6 +73,7 @@ public class PersonController : BaseApiController
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dto"></param>
|
/// <param name="dto"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Authorize("AdminRequired")]
|
||||||
[HttpPost("update")]
|
[HttpPost("update")]
|
||||||
public async Task<ActionResult<PersonDto>> UpdatePerson(UpdatePersonDto dto)
|
public async Task<ActionResult<PersonDto>> UpdatePerson(UpdatePersonDto dto)
|
||||||
{
|
{
|
||||||
|
@ -108,6 +108,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("update-position")]
|
[HttpPost("update-position")]
|
||||||
public async Task<ActionResult> UpdateListItemPosition(UpdateReadingListPosition dto)
|
public async Task<ActionResult> UpdateListItemPosition(UpdateReadingListPosition dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
// Make sure UI buffers events
|
// Make sure UI buffers events
|
||||||
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
@ -129,6 +130,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("delete-item")]
|
[HttpPost("delete-item")]
|
||||||
public async Task<ActionResult> DeleteListItem(UpdateReadingListPosition dto)
|
public async Task<ActionResult> DeleteListItem(UpdateReadingListPosition dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -151,6 +153,8 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("remove-read")]
|
[HttpPost("remove-read")]
|
||||||
public async Task<ActionResult> DeleteReadFromList([FromQuery] int readingListId)
|
public async Task<ActionResult> DeleteReadFromList([FromQuery] int readingListId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
var user = await _readingListService.UserHasReadingListAccess(readingListId, User.GetUsername());
|
var user = await _readingListService.UserHasReadingListAccess(readingListId, User.GetUsername());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -173,6 +177,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public async Task<ActionResult> DeleteList([FromQuery] int readingListId)
|
public async Task<ActionResult> DeleteList([FromQuery] int readingListId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
var user = await _readingListService.UserHasReadingListAccess(readingListId, User.GetUsername());
|
var user = await _readingListService.UserHasReadingListAccess(readingListId, User.GetUsername());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -193,6 +198,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("create")]
|
[HttpPost("create")]
|
||||||
public async Task<ActionResult<ReadingListDto>> CreateList(CreateReadingListDto dto)
|
public async Task<ActionResult<ReadingListDto>> CreateList(CreateReadingListDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.ReadingLists);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.ReadingLists);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
@ -216,6 +222,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("update")]
|
[HttpPost("update")]
|
||||||
public async Task<ActionResult> UpdateList(UpdateReadingListDto dto)
|
public async Task<ActionResult> UpdateList(UpdateReadingListDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(dto.ReadingListId);
|
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(dto.ReadingListId);
|
||||||
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
|
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
|
||||||
|
|
||||||
@ -245,6 +252,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("update-by-series")]
|
[HttpPost("update-by-series")]
|
||||||
public async Task<ActionResult> UpdateListBySeries(UpdateReadingListBySeriesDto dto)
|
public async Task<ActionResult> UpdateListBySeries(UpdateReadingListBySeriesDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -287,6 +295,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("update-by-multiple")]
|
[HttpPost("update-by-multiple")]
|
||||||
public async Task<ActionResult> UpdateListByMultiple(UpdateReadingListByMultipleDto dto)
|
public async Task<ActionResult> UpdateListByMultiple(UpdateReadingListByMultipleDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -331,6 +340,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("update-by-multiple-series")]
|
[HttpPost("update-by-multiple-series")]
|
||||||
public async Task<ActionResult> UpdateListByMultipleSeries(UpdateReadingListByMultipleSeriesDto dto)
|
public async Task<ActionResult> UpdateListByMultipleSeries(UpdateReadingListByMultipleSeriesDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -369,6 +379,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("update-by-volume")]
|
[HttpPost("update-by-volume")]
|
||||||
public async Task<ActionResult> UpdateListByVolume(UpdateReadingListByVolumeDto dto)
|
public async Task<ActionResult> UpdateListByVolume(UpdateReadingListByVolumeDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -405,6 +416,7 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("update-by-chapter")]
|
[HttpPost("update-by-chapter")]
|
||||||
public async Task<ActionResult> UpdateListByChapter(UpdateReadingListByChapterDto dto)
|
public async Task<ActionResult> UpdateListByChapter(UpdateReadingListByChapterDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@ -514,6 +526,8 @@ public class ReadingListController : BaseApiController
|
|||||||
[HttpPost("promote-multiple")]
|
[HttpPost("promote-multiple")]
|
||||||
public async Task<ActionResult> PromoteMultipleReadingLists(PromoteReadingListsDto dto)
|
public async Task<ActionResult> PromoteMultipleReadingLists(PromoteReadingListsDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
// This needs to take into account owner as I can select other users cards
|
// This needs to take into account owner as I can select other users cards
|
||||||
var userId = User.GetUserId();
|
var userId = User.GetUserId();
|
||||||
if (!User.IsInRole(PolicyConstants.PromoteRole) && !User.IsInRole(PolicyConstants.AdminRole))
|
if (!User.IsInRole(PolicyConstants.PromoteRole) && !User.IsInRole(PolicyConstants.AdminRole))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Constants;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.DTOs.Dashboard;
|
using API.DTOs.Dashboard;
|
||||||
using API.DTOs.SideNav;
|
using API.DTOs.SideNav;
|
||||||
@ -19,11 +20,13 @@ public class StreamController : BaseApiController
|
|||||||
{
|
{
|
||||||
private readonly IStreamService _streamService;
|
private readonly IStreamService _streamService;
|
||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
private readonly ILocalizationService _localizationService;
|
||||||
|
|
||||||
public StreamController(IStreamService streamService, IUnitOfWork unitOfWork)
|
public StreamController(IStreamService streamService, IUnitOfWork unitOfWork, ILocalizationService localizationService)
|
||||||
{
|
{
|
||||||
_streamService = streamService;
|
_streamService = streamService;
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
|
_localizationService = localizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -74,6 +77,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpPost("update-external-source")]
|
[HttpPost("update-external-source")]
|
||||||
public async Task<ActionResult<ExternalSourceDto>> UpdateExternalSource(ExternalSourceDto dto)
|
public async Task<ActionResult<ExternalSourceDto>> UpdateExternalSource(ExternalSourceDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
// Check if a host and api key exists for the current user
|
// Check if a host and api key exists for the current user
|
||||||
return Ok(await _streamService.UpdateExternalSource(User.GetUserId(), dto));
|
return Ok(await _streamService.UpdateExternalSource(User.GetUserId(), dto));
|
||||||
}
|
}
|
||||||
@ -86,7 +90,8 @@ public class StreamController : BaseApiController
|
|||||||
[HttpGet("external-source-exists")]
|
[HttpGet("external-source-exists")]
|
||||||
public async Task<ActionResult<bool>> ExternalSourceExists(string host, string name, string apiKey)
|
public async Task<ActionResult<bool>> ExternalSourceExists(string host, string name, string apiKey)
|
||||||
{
|
{
|
||||||
return Ok(await _unitOfWork.AppUserExternalSourceRepository.ExternalSourceExists(User.GetUserId(), host, name, apiKey));
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
return Ok(await _unitOfWork.AppUserExternalSourceRepository.ExternalSourceExists(User.GetUserId(), name, host, apiKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -97,6 +102,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpDelete("delete-external-source")]
|
[HttpDelete("delete-external-source")]
|
||||||
public async Task<ActionResult> ExternalSourceExists(int externalSourceId)
|
public async Task<ActionResult> ExternalSourceExists(int externalSourceId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
await _streamService.DeleteExternalSource(User.GetUserId(), externalSourceId);
|
await _streamService.DeleteExternalSource(User.GetUserId(), externalSourceId);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@ -110,6 +116,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpPost("add-dashboard-stream")]
|
[HttpPost("add-dashboard-stream")]
|
||||||
public async Task<ActionResult<DashboardStreamDto>> AddDashboard([FromQuery] int smartFilterId)
|
public async Task<ActionResult<DashboardStreamDto>> AddDashboard([FromQuery] int smartFilterId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
return Ok(await _streamService.CreateDashboardStreamFromSmartFilter(User.GetUserId(), smartFilterId));
|
return Ok(await _streamService.CreateDashboardStreamFromSmartFilter(User.GetUserId(), smartFilterId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +128,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpPost("update-dashboard-stream")]
|
[HttpPost("update-dashboard-stream")]
|
||||||
public async Task<ActionResult> UpdateDashboardStream(DashboardStreamDto dto)
|
public async Task<ActionResult> UpdateDashboardStream(DashboardStreamDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
await _streamService.UpdateDashboardStream(User.GetUserId(), dto);
|
await _streamService.UpdateDashboardStream(User.GetUserId(), dto);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@ -133,6 +141,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpPost("update-dashboard-position")]
|
[HttpPost("update-dashboard-position")]
|
||||||
public async Task<ActionResult> UpdateDashboardStreamPosition(UpdateStreamPositionDto dto)
|
public async Task<ActionResult> UpdateDashboardStreamPosition(UpdateStreamPositionDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
await _streamService.UpdateDashboardStreamPosition(User.GetUserId(), dto);
|
await _streamService.UpdateDashboardStreamPosition(User.GetUserId(), dto);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@ -146,6 +155,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpPost("add-sidenav-stream")]
|
[HttpPost("add-sidenav-stream")]
|
||||||
public async Task<ActionResult<SideNavStreamDto>> AddSideNav([FromQuery] int smartFilterId)
|
public async Task<ActionResult<SideNavStreamDto>> AddSideNav([FromQuery] int smartFilterId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
return Ok(await _streamService.CreateSideNavStreamFromSmartFilter(User.GetUserId(), smartFilterId));
|
return Ok(await _streamService.CreateSideNavStreamFromSmartFilter(User.GetUserId(), smartFilterId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +167,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpPost("add-sidenav-stream-from-external-source")]
|
[HttpPost("add-sidenav-stream-from-external-source")]
|
||||||
public async Task<ActionResult<SideNavStreamDto>> AddSideNavFromExternalSource([FromQuery] int externalSourceId)
|
public async Task<ActionResult<SideNavStreamDto>> AddSideNavFromExternalSource([FromQuery] int externalSourceId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
return Ok(await _streamService.CreateSideNavStreamFromExternalSource(User.GetUserId(), externalSourceId));
|
return Ok(await _streamService.CreateSideNavStreamFromExternalSource(User.GetUserId(), externalSourceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +179,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpPost("update-sidenav-stream")]
|
[HttpPost("update-sidenav-stream")]
|
||||||
public async Task<ActionResult> UpdateSideNavStream(SideNavStreamDto dto)
|
public async Task<ActionResult> UpdateSideNavStream(SideNavStreamDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
await _streamService.UpdateSideNavStream(User.GetUserId(), dto);
|
await _streamService.UpdateSideNavStream(User.GetUserId(), dto);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@ -180,6 +192,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpPost("update-sidenav-position")]
|
[HttpPost("update-sidenav-position")]
|
||||||
public async Task<ActionResult> UpdateSideNavStreamPosition(UpdateStreamPositionDto dto)
|
public async Task<ActionResult> UpdateSideNavStreamPosition(UpdateStreamPositionDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
await _streamService.UpdateSideNavStreamPosition(User.GetUserId(), dto);
|
await _streamService.UpdateSideNavStreamPosition(User.GetUserId(), dto);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@ -187,6 +200,7 @@ public class StreamController : BaseApiController
|
|||||||
[HttpPost("bulk-sidenav-stream-visibility")]
|
[HttpPost("bulk-sidenav-stream-visibility")]
|
||||||
public async Task<ActionResult> BulkUpdateSideNavStream(BulkUpdateSideNavStreamVisibilityDto dto)
|
public async Task<ActionResult> BulkUpdateSideNavStream(BulkUpdateSideNavStreamVisibilityDto dto)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
await _streamService.UpdateSideNavStreamBulk(User.GetUserId(), dto);
|
await _streamService.UpdateSideNavStreamBulk(User.GetUserId(), dto);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ public class ThemeController : BaseApiController
|
|||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
public async Task<ActionResult<IEnumerable<DownloadableSiteThemeDto>>> DeleteTheme(int themeId)
|
public async Task<ActionResult<IEnumerable<DownloadableSiteThemeDto>>> DeleteTheme(int themeId)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
await _themeService.DeleteTheme(themeId);
|
await _themeService.DeleteTheme(themeId);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
@ -128,6 +128,8 @@ public class ThemeController : BaseApiController
|
|||||||
[HttpPost("upload-theme")]
|
[HttpPost("upload-theme")]
|
||||||
public async Task<ActionResult<SiteThemeDto>> DownloadTheme(IFormFile formFile)
|
public async Task<ActionResult<SiteThemeDto>> DownloadTheme(IFormFile formFile)
|
||||||
{
|
{
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
if (!formFile.FileName.EndsWith(".css")) return BadRequest("Invalid file");
|
if (!formFile.FileName.EndsWith(".css")) return BadRequest("Invalid file");
|
||||||
if (formFile.FileName.Contains("..")) return BadRequest("Invalid file");
|
if (formFile.FileName.Contains("..")) return BadRequest("Invalid file");
|
||||||
var tempFile = await UploadToTemp(formFile);
|
var tempFile = await UploadToTemp(formFile);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Constants;
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
@ -82,12 +83,20 @@ public class UsersController : BaseApiController
|
|||||||
return Ok(libs.Any(x => x.Id == libraryId));
|
return Ok(libs.Any(x => x.Id == libraryId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the user preferences
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>If the user has ReadOnly role, they will not be able to perform this action</remarks>
|
||||||
|
/// <param name="preferencesDto"></param>
|
||||||
|
/// <returns></returns>
|
||||||
[HttpPost("update-preferences")]
|
[HttpPost("update-preferences")]
|
||||||
public async Task<ActionResult<UserPreferencesDto>> UpdatePreferences(UserPreferencesDto preferencesDto)
|
public async Task<ActionResult<UserPreferencesDto>> UpdatePreferences(UserPreferencesDto preferencesDto)
|
||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
|
||||||
AppUserIncludes.UserPreferences);
|
AppUserIncludes.UserPreferences);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
|
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||||
|
|
||||||
var existingPreferences = user!.UserPreferences;
|
var existingPreferences = user!.UserPreferences;
|
||||||
|
|
||||||
existingPreferences.ReadingDirection = preferencesDto.ReadingDirection;
|
existingPreferences.ReadingDirection = preferencesDto.ReadingDirection;
|
||||||
|
@ -89,7 +89,9 @@ public class StatisticService : IStatisticService
|
|||||||
|
|
||||||
var lastActive = await _context.AppUserProgresses
|
var lastActive = await _context.AppUserProgresses
|
||||||
.Where(p => p.AppUserId == userId)
|
.Where(p => p.AppUserId == userId)
|
||||||
.MaxAsync(p => p.LastModified);
|
.Select(p => p.LastModified)
|
||||||
|
.DefaultIfEmpty()
|
||||||
|
.MaxAsync();
|
||||||
|
|
||||||
|
|
||||||
// First get the total pages per library
|
// First get the total pages per library
|
||||||
@ -127,12 +129,23 @@ public class StatisticService : IStatisticService
|
|||||||
|
|
||||||
var earliestReadDate = await _context.AppUserProgresses
|
var earliestReadDate = await _context.AppUserProgresses
|
||||||
.Where(p => p.AppUserId == userId)
|
.Where(p => p.AppUserId == userId)
|
||||||
.MinAsync(p => p.Created);
|
.Select(p => p.Created)
|
||||||
|
.DefaultIfEmpty()
|
||||||
|
.MinAsync();
|
||||||
|
|
||||||
|
if (earliestReadDate == DateTime.MinValue)
|
||||||
|
{
|
||||||
|
averageReadingTimePerWeek = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var timeDifference = DateTime.Now - earliestReadDate;
|
||||||
|
var deltaWeeks = (int)Math.Ceiling(timeDifference.TotalDays / 7);
|
||||||
|
|
||||||
|
averageReadingTimePerWeek /= deltaWeeks;
|
||||||
|
}
|
||||||
|
|
||||||
var timeDifference = DateTime.Now - earliestReadDate;
|
|
||||||
var deltaWeeks = (int)Math.Ceiling(timeDifference.TotalDays / 7);
|
|
||||||
|
|
||||||
averageReadingTimePerWeek /= deltaWeeks;
|
|
||||||
|
|
||||||
|
|
||||||
return new UserReadStatistics()
|
return new UserReadStatistics()
|
||||||
|
1
UI/Web/.gitignore
vendored
1
UI/Web/.gitignore
vendored
@ -2,3 +2,4 @@ node_modules/
|
|||||||
test-results/
|
test-results/
|
||||||
playwright-report/
|
playwright-report/
|
||||||
i18n-cache-busting.json
|
i18n-cache-busting.json
|
||||||
|
e2e-tests/environments/environment.local.ts
|
||||||
|
@ -6,21 +6,30 @@ import {Directive, EventEmitter, HostListener, Output} from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class DblClickDirective {
|
export class DblClickDirective {
|
||||||
|
|
||||||
|
@Output() singleClick = new EventEmitter<Event>();
|
||||||
@Output() doubleClick = new EventEmitter<Event>();
|
@Output() doubleClick = new EventEmitter<Event>();
|
||||||
|
|
||||||
private lastTapTime = 0;
|
private lastTapTime = 0;
|
||||||
private tapTimeout = 300; // Time threshold for a double tap (in milliseconds)
|
private tapTimeout = 300; // Time threshold for a double tap (in milliseconds)
|
||||||
|
private singleClickTimeout: any;
|
||||||
|
|
||||||
@HostListener('click', ['$event'])
|
@HostListener('click', ['$event'])
|
||||||
handleClick(event: Event): void {
|
handleClick(event: Event): void {
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const currentTime = new Date().getTime();
|
const currentTime = new Date().getTime();
|
||||||
|
|
||||||
if (currentTime - this.lastTapTime < this.tapTimeout) {
|
if (currentTime - this.lastTapTime < this.tapTimeout) {
|
||||||
// Detected a double click/tap
|
// Detected a double click/tap
|
||||||
|
clearTimeout(this.singleClickTimeout); // Prevent single-click emission
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
this.doubleClick.emit(event);
|
this.doubleClick.emit(event);
|
||||||
|
} else {
|
||||||
|
// Delay single-click emission to check if a double-click occurs
|
||||||
|
this.singleClickTimeout = setTimeout(() => {
|
||||||
|
this.singleClick.emit(event); // Optional: emit single-click if no double-click follows
|
||||||
|
}, this.tapTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastTapTime = currentTime;
|
this.lastTapTime = currentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import { AgeRestriction } from '../_models/metadata/age-restriction';
|
|||||||
import { TextResonse } from '../_types/text-response';
|
import { TextResonse } from '../_types/text-response';
|
||||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
import {Action} from "./action-factory.service";
|
import {Action} from "./action-factory.service";
|
||||||
|
import {CoverImageSize} from "../admin/_models/cover-image-size";
|
||||||
|
|
||||||
export enum Role {
|
export enum Role {
|
||||||
Admin = 'Admin',
|
Admin = 'Admin',
|
||||||
@ -27,6 +28,17 @@ export enum Role {
|
|||||||
Promote = 'Promote',
|
Promote = 'Promote',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const allRoles = [
|
||||||
|
Role.Admin,
|
||||||
|
Role.ChangePassword,
|
||||||
|
Role.Bookmark,
|
||||||
|
Role.Download,
|
||||||
|
Role.ChangeRestriction,
|
||||||
|
Role.ReadOnly,
|
||||||
|
Role.Login,
|
||||||
|
Role.Promote,
|
||||||
|
]
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@ -91,14 +103,22 @@ export class AccountService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasAnyRole(user: User, roles: Array<Role>) {
|
hasAnyRole(user: User, roles: Array<Role>, restrictedRoles: Array<Role> = []) {
|
||||||
if (!user || !user.roles) {
|
if (!user || !user.roles) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If restricted roles are provided and the user has any of them, deny access
|
||||||
|
if (restrictedRoles.length > 0 && restrictedRoles.some(role => user.roles.includes(role))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If roles are empty, allow access (no restrictions by roles)
|
||||||
if (roles.length === 0) {
|
if (roles.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow access if the user has any of the allowed roles
|
||||||
return roles.some(role => user.roles.includes(role));
|
return roles.some(role => user.roles.includes(role));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +123,7 @@
|
|||||||
filter: blur(20px);
|
filter: blur(20px);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
|
mix-blend-mode: color;
|
||||||
|
|
||||||
.background-area {
|
.background-area {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : '100dvh'}" #readingArea>
|
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : '100dvh'}" #readingArea>
|
||||||
|
|
||||||
@if (readerMode !== ReaderMode.Webtoon) {
|
@if (readerMode !== ReaderMode.Webtoon) {
|
||||||
<div (dblclick)="bookmarkPage($event)">
|
<div appDblClick (dblclick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
|
||||||
<app-canvas-renderer
|
<app-canvas-renderer
|
||||||
[readerSettings$]="readerSettings$"
|
[readerSettings$]="readerSettings$"
|
||||||
[image$]="currentImage$"
|
[image$]="currentImage$"
|
||||||
@ -89,7 +89,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div appDblClick (doubleClick)="bookmarkPage($event)">
|
<div appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
|
||||||
<app-single-renderer [image$]="currentImage$"
|
<app-single-renderer [image$]="currentImage$"
|
||||||
[readerSettings$]="readerSettings$"
|
[readerSettings$]="readerSettings$"
|
||||||
[bookmark$]="showBookmarkEffect$"
|
[bookmark$]="showBookmarkEffect$"
|
||||||
@ -123,7 +123,7 @@
|
|||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
@if (!isLoading && !inSetup) {
|
@if (!isLoading && !inSetup) {
|
||||||
<div class="webtoon-images">
|
<div class="webtoon-images" appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
|
||||||
<app-infinite-scroller [pageNum]="pageNum"
|
<app-infinite-scroller [pageNum]="pageNum"
|
||||||
[bufferPages]="5"
|
[bufferPages]="5"
|
||||||
[goToPage]="goToPageEvent"
|
[goToPage]="goToPageEvent"
|
||||||
|
@ -623,10 +623,10 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
fromEvent(this.readingArea.nativeElement, 'click').pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef)).subscribe((event: MouseEvent | any) => {
|
// fromEvent(this.readingArea.nativeElement, 'click').pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef)).subscribe((event: MouseEvent | any) => {
|
||||||
if (event.detail > 1) return;
|
// if (event.detail > 1) return;
|
||||||
this.toggleMenu();
|
// this.toggleMenu();
|
||||||
});
|
// });
|
||||||
|
|
||||||
fromEvent(this.readingArea.nativeElement, 'scroll').pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
fromEvent(this.readingArea.nativeElement, 'scroll').pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||||
this.prevScrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0;
|
this.prevScrollLeft = this.readingArea?.nativeElement?.scrollLeft || 0;
|
||||||
@ -1663,6 +1663,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
if (this.bookmarkMode) return;
|
if (this.bookmarkMode) return;
|
||||||
|
if (!(this.accountService.hasBookmarkRole(this.user) || this.accountService.hasAdminRole(this.user))) return;
|
||||||
|
|
||||||
const pageNum = this.pageNum;
|
const pageNum = this.pageNum;
|
||||||
// if canvasRenderer and doubleRenderer is undefined, then we are in webtoon mode
|
// if canvasRenderer and doubleRenderer is undefined, then we are in webtoon mode
|
||||||
|
@ -11,7 +11,9 @@
|
|||||||
@if (navStreams$ | async; as streams) {
|
@if (navStreams$ | async; as streams) {
|
||||||
@if (showAll) {
|
@if (showAll) {
|
||||||
<app-side-nav-item icon="fa fa-chevron-left" [title]="t('back')" (click)="showLess()"></app-side-nav-item>
|
<app-side-nav-item icon="fa fa-chevron-left" [title]="t('back')" (click)="showLess()"></app-side-nav-item>
|
||||||
<app-side-nav-item icon="fa-cogs" [title]="t('customize')" link="/settings" [fragment]="SettingsTabId.Customize"></app-side-nav-item>
|
@if (!isReadOnly) {
|
||||||
|
<app-side-nav-item icon="fa-cogs" [title]="t('customize')" link="/settings" [fragment]="SettingsTabId.Customize"></app-side-nav-item>
|
||||||
|
}
|
||||||
@if (streams.length > ItemLimit && (navService.sideNavCollapsed$ | async) === false) {
|
@if (streams.length > ItemLimit && (navService.sideNavCollapsed$ | async) === false) {
|
||||||
<div class="mb-2 mt-3 ms-2 me-2">
|
<div class="mb-2 mt-3 ms-2 me-2">
|
||||||
<label for="filter" class="form-label visually-hidden">{{t('filter-label')}}</label>
|
<label for="filter" class="form-label visually-hidden">{{t('filter-label')}}</label>
|
||||||
|
@ -66,6 +66,7 @@ export class SideNavComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
showAll: boolean = false;
|
showAll: boolean = false;
|
||||||
totalSize = 0;
|
totalSize = 0;
|
||||||
|
isReadOnly = false;
|
||||||
|
|
||||||
private showAllSubject = new BehaviorSubject<boolean>(false);
|
private showAllSubject = new BehaviorSubject<boolean>(false);
|
||||||
showAll$ = this.showAllSubject.asObservable();
|
showAll$ = this.showAllSubject.asObservable();
|
||||||
@ -146,6 +147,8 @@ export class SideNavComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
this.isReadOnly = this.accountService.hasReadOnlyRole(user!);
|
||||||
|
this.cdRef.markForCheck();
|
||||||
this.loadDataSubject.next();
|
this.loadDataSubject.next();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
@if (hasAnyChildren(user, section)) {
|
@if (hasAnyChildren(user, section)) {
|
||||||
<h5 class="side-nav-header mb-2 ms-3" [ngClass]="{'mt-4': idx > 0}">{{t(section.title)}}</h5>
|
<h5 class="side-nav-header mb-2 ms-3" [ngClass]="{'mt-4': idx > 0}">{{t(section.title)}}</h5>
|
||||||
@for(item of section.children; track item.fragment) {
|
@for(item of section.children; track item.fragment) {
|
||||||
@if (accountService.hasAnyRole(user, item.roles)) {
|
@if (accountService.hasAnyRole(user, item.roles, item.restrictRoles)) {
|
||||||
<app-side-nav-item [id]="'nav-item-' + item.fragment" [noIcon]="true" link="/settings" [fragment]="item.fragment" [title]="item.fragment | settingFragment" [badgeCount]="item.badgeCount$ | async"></app-side-nav-item>
|
<app-side-nav-item [id]="'nav-item-' + item.fragment" [noIcon]="true" link="/settings" [fragment]="item.fragment" [title]="item.fragment | settingFragment" [badgeCount]="item.badgeCount$ | async"></app-side-nav-item>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import {AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, De
|
|||||||
import {TranslocoDirective} from "@jsverse/transloco";
|
import {TranslocoDirective} from "@jsverse/transloco";
|
||||||
import {AsyncPipe, DOCUMENT, NgClass} from "@angular/common";
|
import {AsyncPipe, DOCUMENT, NgClass} from "@angular/common";
|
||||||
import {NavService} from "../../_services/nav.service";
|
import {NavService} from "../../_services/nav.service";
|
||||||
import {AccountService, Role} from "../../_services/account.service";
|
import {AccountService, allRoles, Role} from "../../_services/account.service";
|
||||||
import {SideNavItemComponent} from "../_components/side-nav-item/side-nav-item.component";
|
import {SideNavItemComponent} from "../_components/side-nav-item/side-nav-item.component";
|
||||||
import {ActivatedRoute, NavigationEnd, Router, RouterLink} from "@angular/router";
|
import {ActivatedRoute, NavigationEnd, Router, RouterLink} from "@angular/router";
|
||||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
@ -51,11 +51,16 @@ interface PrefSection {
|
|||||||
class SideNavItem {
|
class SideNavItem {
|
||||||
fragment: SettingsTabId;
|
fragment: SettingsTabId;
|
||||||
roles: Array<Role> = [];
|
roles: Array<Role> = [];
|
||||||
|
/**
|
||||||
|
* If you have any of these, the item will be restricted
|
||||||
|
*/
|
||||||
|
restrictRoles: Array<Role> = [];
|
||||||
badgeCount$?: Observable<number> | undefined;
|
badgeCount$?: Observable<number> | undefined;
|
||||||
|
|
||||||
constructor(fragment: SettingsTabId, roles: Array<Role> = [], badgeCount$: Observable<number> | undefined = undefined) {
|
constructor(fragment: SettingsTabId, roles: Array<Role> = [], badgeCount$: Observable<number> | undefined = undefined, restrictRoles: Array<Role> = []) {
|
||||||
this.fragment = fragment;
|
this.fragment = fragment;
|
||||||
this.roles = roles;
|
this.roles = roles;
|
||||||
|
this.restrictRoles = restrictRoles;
|
||||||
this.badgeCount$ = badgeCount$;
|
this.badgeCount$ = badgeCount$;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,7 +73,6 @@ class SideNavItem {
|
|||||||
NgClass,
|
NgClass,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
SideNavItemComponent,
|
SideNavItemComponent,
|
||||||
RouterLink,
|
|
||||||
SettingFragmentPipe
|
SettingFragmentPipe
|
||||||
],
|
],
|
||||||
templateUrl: './preference-nav.component.html',
|
templateUrl: './preference-nav.component.html',
|
||||||
@ -98,7 +102,7 @@ export class PreferenceNavComponent implements AfterViewInit {
|
|||||||
children: [
|
children: [
|
||||||
new SideNavItem(SettingsTabId.Account, []),
|
new SideNavItem(SettingsTabId.Account, []),
|
||||||
new SideNavItem(SettingsTabId.Preferences),
|
new SideNavItem(SettingsTabId.Preferences),
|
||||||
new SideNavItem(SettingsTabId.Customize),
|
new SideNavItem(SettingsTabId.Customize, [], undefined, [Role.ReadOnly]),
|
||||||
new SideNavItem(SettingsTabId.Clients),
|
new SideNavItem(SettingsTabId.Clients),
|
||||||
new SideNavItem(SettingsTabId.Theme),
|
new SideNavItem(SettingsTabId.Theme),
|
||||||
new SideNavItem(SettingsTabId.Devices),
|
new SideNavItem(SettingsTabId.Devices),
|
||||||
@ -119,7 +123,7 @@ export class PreferenceNavComponent implements AfterViewInit {
|
|||||||
{
|
{
|
||||||
title: 'import-section-title',
|
title: 'import-section-title',
|
||||||
children: [
|
children: [
|
||||||
new SideNavItem(SettingsTabId.CBLImport, []),
|
new SideNavItem(SettingsTabId.CBLImport, [], undefined, [Role.ReadOnly]),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -175,7 +179,6 @@ export class PreferenceNavComponent implements AfterViewInit {
|
|||||||
this.navService.collapseSideNav(true);
|
this.navService.collapseSideNav(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.accountService.hasValidLicense$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(res => {
|
this.accountService.hasValidLicense$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(res => {
|
||||||
if (res) {
|
if (res) {
|
||||||
this.hasActiveLicense = true;
|
this.hasActiveLicense = true;
|
||||||
@ -203,7 +206,6 @@ export class PreferenceNavComponent implements AfterViewInit {
|
|||||||
if (this.sections[2].children.length === 1) {
|
if (this.sections[2].children.length === 1) {
|
||||||
this.sections[2].children.push(new SideNavItem(SettingsTabId.MALStackImport, []));
|
this.sections[2].children.push(new SideNavItem(SettingsTabId.MALStackImport, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scrollToActiveItem();
|
this.scrollToActiveItem();
|
||||||
@ -227,7 +229,15 @@ export class PreferenceNavComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasAnyChildren(user: User, section: PrefSection) {
|
hasAnyChildren(user: User, section: PrefSection) {
|
||||||
return section.children.filter(item => this.accountService.hasAnyRole(user, item.roles)).length > 0;
|
// Filter out items where the user has a restricted role
|
||||||
|
const visibleItems = section.children.filter(item =>
|
||||||
|
item.restrictRoles.length === 0 || !this.accountService.hasAnyRole(user, item.restrictRoles)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if the user has any allowed roles in the remaining items
|
||||||
|
return visibleItems.some(item =>
|
||||||
|
this.accountService.hasAnyRole(user, item.roles)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
collapse() {
|
collapse() {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<ng-container *transloco="let t; read:'change-email'">
|
<ng-container *transloco="let t; read:'change-email'">
|
||||||
|
|
||||||
<app-setting-item [title]="t('email-title')">
|
<app-setting-item [title]="t('email-title')" [canEdit]="canEdit">
|
||||||
<ng-template #extra>
|
<ng-template #extra>
|
||||||
@if(emailConfirmed) {
|
@if(emailConfirmed) {
|
||||||
<i class="fa-solid fa-circle-check ms-1 confirm-icon" aria-hidden="true" [ngbTooltip]="t('email-confirmed')"></i>
|
<i class="fa-solid fa-circle-check ms-1 confirm-icon" aria-hidden="true" [ngbTooltip]="t('email-confirmed')"></i>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<ng-container *transloco="let t; read:'change-password'">
|
<ng-container *transloco="let t; read:'change-password'">
|
||||||
<app-setting-item [title]="t('password-label')">
|
<app-setting-item [title]="t('password-label')" [canEdit]="canEdit">
|
||||||
<ng-template #view>
|
<ng-template #view>
|
||||||
<span class="col-12">***************</span>
|
<span class="col-12">***************</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -41,6 +41,7 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
|
|||||||
passwordsMatch = false;
|
passwordsMatch = false;
|
||||||
resetPasswordErrors: string[] = [];
|
resetPasswordErrors: string[] = [];
|
||||||
isViewMode: boolean = true;
|
isViewMode: boolean = true;
|
||||||
|
canEdit: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
public get password() { return this.passwordChangeForm.get('password'); }
|
public get password() { return this.passwordChangeForm.get('password'); }
|
||||||
@ -50,6 +51,7 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay()).subscribe(user => {
|
this.accountService.currentUser$.pipe(takeUntilDestroyed(this.destroyRef), shareReplay()).subscribe(user => {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
this.canEdit = !this.accountService.hasReadOnlyRole(user!);
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<ng-container *transloco="let t; read:'manage-devices'">
|
<ng-container *transloco="let t; read:'manage-devices'">
|
||||||
|
|
||||||
<div class="position-relative">
|
<div class="position-relative">
|
||||||
<button class="btn btn-primary-outline position-absolute custom-position" (click)="addDevice()">
|
<button class="btn btn-primary-outline position-absolute custom-position" [disabled]="isReadOnly$" (click)="addDevice()">
|
||||||
<i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden ms-1">{{t('add')}}</span>
|
<i class="fa fa-plus" aria-hidden="true"></i><span class="phone-hidden ms-1">{{t('add')}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component, DestroyRef,
|
||||||
inject,
|
inject,
|
||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
@ -20,6 +20,10 @@ import {SortableHeader} from "../../_single-module/table/_directives/sortable-he
|
|||||||
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
|
import {UtcToLocalTimePipe} from "../../_pipes/utc-to-local-time.pipe";
|
||||||
import {EditDeviceModalComponent} from "../_modals/edit-device-modal/edit-device-modal.component";
|
import {EditDeviceModalComponent} from "../_modals/edit-device-modal/edit-device-modal.component";
|
||||||
import {DefaultModalOptions} from "../../_models/default-modal-options";
|
import {DefaultModalOptions} from "../../_models/default-modal-options";
|
||||||
|
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
|
import {map} from "rxjs";
|
||||||
|
import {shareReplay} from "rxjs/operators";
|
||||||
|
import {AccountService} from "../../_services/account.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-manage-devices',
|
selector: 'app-manage-devices',
|
||||||
@ -33,16 +37,24 @@ import {DefaultModalOptions} from "../../_models/default-modal-options";
|
|||||||
export class ManageDevicesComponent implements OnInit {
|
export class ManageDevicesComponent implements OnInit {
|
||||||
|
|
||||||
private readonly cdRef = inject(ChangeDetectorRef);
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
private readonly deviceService = inject(DeviceService);
|
private readonly deviceService = inject(DeviceService);
|
||||||
private readonly settingsService = inject(SettingsService);
|
private readonly settingsService = inject(SettingsService);
|
||||||
private readonly confirmService = inject(ConfirmService);
|
private readonly confirmService = inject(ConfirmService);
|
||||||
private readonly modalService = inject(NgbModal);
|
private readonly modalService = inject(NgbModal);
|
||||||
|
private readonly accountService = inject(AccountService);
|
||||||
|
|
||||||
devices: Array<Device> = [];
|
devices: Array<Device> = [];
|
||||||
isEditingDevice: boolean = false;
|
isEditingDevice: boolean = false;
|
||||||
device: Device | undefined;
|
device: Device | undefined;
|
||||||
hasEmailSetup = false;
|
hasEmailSetup = false;
|
||||||
|
|
||||||
|
isReadOnly$ = this.accountService.currentUser$.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
map(c => c && this.accountService.hasReadOnlyRole(c)),
|
||||||
|
shareReplay({refCount: true, bufferSize: 1}),
|
||||||
|
);
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.settingsService.isEmailSetup().subscribe(res => {
|
this.settingsService.isEmailSetup().subscribe(res => {
|
||||||
this.hasEmailSetup = res;
|
this.hasEmailSetup = res;
|
||||||
|
@ -87,14 +87,14 @@
|
|||||||
{{selectedTheme.name | sentenceCase}}
|
{{selectedTheme.name | sentenceCase}}
|
||||||
<div class="float-end">
|
<div class="float-end">
|
||||||
@if (selectedTheme.isSiteTheme) {
|
@if (selectedTheme.isSiteTheme) {
|
||||||
@if (selectedTheme.name !== 'Dark') {
|
@if (selectedTheme.name !== 'Dark' && (canUseThemes$ | async)) {
|
||||||
<button class="btn btn-danger me-1" (click)="deleteTheme(selectedTheme.site!)">{{t('delete')}}</button>
|
<button class="btn btn-danger me-1" (click)="deleteTheme(selectedTheme.site!)">{{t('delete')}}</button>
|
||||||
}
|
}
|
||||||
@if (hasAdmin$ | async) {
|
@if (hasAdmin$ | async) {
|
||||||
<button class="btn btn-secondary me-1" [disabled]="selectedTheme.site?.isDefault" (click)="updateDefault(selectedTheme.site!)">{{t('set-default')}}</button>
|
<button class="btn btn-secondary me-1" [disabled]="selectedTheme.site?.isDefault" (click)="updateDefault(selectedTheme.site!)">{{t('set-default')}}</button>
|
||||||
}
|
}
|
||||||
<button class="btn btn-primary me-1" [disabled]="currentTheme && selectedTheme.name === currentTheme.name" (click)="applyTheme(selectedTheme.site!)">{{t('apply')}}</button>
|
<button class="btn btn-primary me-1" [disabled]="currentTheme && selectedTheme.name === currentTheme.name" (click)="applyTheme(selectedTheme.site!)">{{t('apply')}}</button>
|
||||||
} @else {
|
} @else if (canUseThemes$ | async) {
|
||||||
<button class="btn btn-primary" [disabled]="selectedTheme.downloadable?.alreadyDownloaded" (click)="downloadTheme(selectedTheme.downloadable!)">{{t('download')}}</button>
|
<button class="btn btn-primary" [disabled]="selectedTheme.downloadable?.alreadyDownloaded" (click)="downloadTheme(selectedTheme.downloadable!)">{{t('download')}}</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,6 +73,12 @@ export class ThemeManagerComponent {
|
|||||||
shareReplay({refCount: true, bufferSize: 1}),
|
shareReplay({refCount: true, bufferSize: 1}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
canUseThemes$ = this.accountService.currentUser$.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
map(c => c && !this.accountService.hasReadOnlyRole(c)),
|
||||||
|
shareReplay({refCount: true, bufferSize: 1}),
|
||||||
|
);
|
||||||
|
|
||||||
files: NgxFileDropEntry[] = [];
|
files: NgxFileDropEntry[] = [];
|
||||||
acceptableExtensions = ['.css'].join(',');
|
acceptableExtensions = ['.css'].join(',');
|
||||||
isUploadingTheme: boolean = false;
|
isUploadingTheme: boolean = false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user