Polish for Release (#2314)
This commit is contained in:
parent
fe4af4b648
commit
59b950c4bd
54 changed files with 1162 additions and 1056 deletions
|
|
@ -68,22 +68,22 @@
|
|||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.3.4" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.53" />
|
||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.12" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.12" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.12" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.11">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.12">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.12" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
|
||||
<PackageReference Include="Nager.ArticleNumber" Version="1.0.7" />
|
||||
<PackageReference Include="NetVips" Version="2.3.1" />
|
||||
<PackageReference Include="NetVips.Native" Version="8.14.5" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.1.6" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.1.7" />
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.2.0-dev-00752" />
|
||||
|
|
@ -93,15 +93,15 @@
|
|||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
|
||||
<PackageReference Include="SharpCompress" Version="0.34.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.34.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.2" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.10.0.77988">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.12.0.78982">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.11" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.2" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="19.2.69" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.3.1" />
|
||||
|
|
|
|||
|
|
@ -306,12 +306,13 @@ public class AccountController : BaseApiController
|
|||
/// <summary>
|
||||
/// Resets the API Key assigned with a user
|
||||
/// </summary>
|
||||
/// <remarks>This will log unauthorized requests to Security log</remarks>
|
||||
/// <returns></returns>
|
||||
[HttpPost("reset-api-key")]
|
||||
public async Task<ActionResult<string>> ResetApiKey()
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
if (user == null) return Unauthorized();
|
||||
if (user == null) throw new KavitaUnauthenticatedUserException();
|
||||
|
||||
user.ApiKey = HashUtil.ApiKey();
|
||||
|
||||
|
|
|
|||
|
|
@ -98,8 +98,11 @@ public class LibraryController : BaseApiController
|
|||
admin.Libraries.Add(library);
|
||||
}
|
||||
|
||||
var userIds = admins.Select(u => u.Id).Append(User.GetUserId()).ToList();
|
||||
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-library"));
|
||||
_logger.LogInformation("Created a new library: {LibraryName}", library.Name);
|
||||
|
||||
// Assign all the necessary users with this library side nav
|
||||
var userIds = admins.Select(u => u.Id).Append(User.GetUserId()).ToList();
|
||||
var userNeedingNewLibrary = (await _unitOfWork.UserRepository.GetAllUsersAsync(AppUserIncludes.SideNavStreams))
|
||||
.Where(u => userIds.Contains(u.Id))
|
||||
.ToList();
|
||||
|
|
@ -119,10 +122,8 @@ public class LibraryController : BaseApiController
|
|||
_unitOfWork.UserRepository.Update(user);
|
||||
}
|
||||
|
||||
|
||||
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-library"));
|
||||
|
||||
_logger.LogInformation("Created a new library: {LibraryName}", library.Name);
|
||||
await _libraryWatcher.RestartWatching();
|
||||
_taskScheduler.ScanLibrary(library.Id);
|
||||
await _eventHub.SendMessageAsync(MessageFactory.LibraryModified,
|
||||
|
|
|
|||
|
|
@ -216,19 +216,41 @@ public class OpdsController : BaseApiController
|
|||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/collections"),
|
||||
}
|
||||
});
|
||||
feed.Entries.Add(new FeedEntry()
|
||||
|
||||
if ((_unitOfWork.AppUserSmartFilterRepository.GetAllDtosByUserId(userId)).Any())
|
||||
{
|
||||
Id = "allSmartFilters",
|
||||
Title = await _localizationService.Translate(userId, "smart-filters"),
|
||||
Content = new FeedEntryContent()
|
||||
feed.Entries.Add(new FeedEntry()
|
||||
{
|
||||
Text = await _localizationService.Translate(userId, "browse-smart-filters")
|
||||
},
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/smart-filters"),
|
||||
}
|
||||
});
|
||||
Id = "allSmartFilters",
|
||||
Title = await _localizationService.Translate(userId, "smart-filters"),
|
||||
Content = new FeedEntryContent()
|
||||
{
|
||||
Text = await _localizationService.Translate(userId, "browse-smart-filters")
|
||||
},
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/smart-filters"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// if ((await _unitOfWork.AppUserExternalSourceRepository.GetExternalSources(userId)).Any())
|
||||
// {
|
||||
// feed.Entries.Add(new FeedEntry()
|
||||
// {
|
||||
// Id = "allExternalSources",
|
||||
// Title = await _localizationService.Translate(userId, "external-sources"),
|
||||
// Content = new FeedEntryContent()
|
||||
// {
|
||||
// Text = await _localizationService.Translate(userId, "browse-external-sources")
|
||||
// },
|
||||
// Links = new List<FeedLink>()
|
||||
// {
|
||||
// CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/external-sources"),
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
return CreateXmlResult(SerializeXml(feed));
|
||||
}
|
||||
|
||||
|
|
@ -306,6 +328,38 @@ public class OpdsController : BaseApiController
|
|||
return CreateXmlResult(SerializeXml(feed));
|
||||
}
|
||||
|
||||
[HttpGet("{apiKey}/external-sources")]
|
||||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> GetExternalSources(string apiKey)
|
||||
{
|
||||
// NOTE: This doesn't seem possible in OPDS v2.1 due to the resulting stream using relative links and most apps resolve against source url. Even using full paths doesn't work
|
||||
var userId = await GetUser(apiKey);
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
|
||||
var externalSources = await _unitOfWork.AppUserExternalSourceRepository.GetExternalSources(userId);
|
||||
var feed = CreateFeed(await _localizationService.Translate(userId, "external-sources"), $"{prefix}{apiKey}/external-sources", apiKey, prefix);
|
||||
SetFeedId(feed, "externalSources");
|
||||
foreach (var externalSource in externalSources)
|
||||
{
|
||||
var opdsUrl = $"{externalSource.Host}api/opds/{externalSource.ApiKey}";
|
||||
feed.Entries.Add(new FeedEntry()
|
||||
{
|
||||
Id = externalSource.Id.ToString(),
|
||||
Title = externalSource.Name,
|
||||
Summary = externalSource.Host,
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
CreateLink(FeedLinkRelation.Start, FeedLinkType.AtomNavigation, opdsUrl),
|
||||
CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"{opdsUrl}/favicon")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return CreateXmlResult(SerializeXml(feed));
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{apiKey}/libraries")]
|
||||
[Produces("application/xml")]
|
||||
|
|
@ -318,12 +372,16 @@ public class OpdsController : BaseApiController
|
|||
var libraries = await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId);
|
||||
var feed = CreateFeed(await _localizationService.Translate(userId, "libraries"), $"{prefix}{apiKey}/libraries", apiKey, prefix);
|
||||
SetFeedId(feed, "libraries");
|
||||
foreach (var library in libraries)
|
||||
|
||||
// Ensure libraries follow SideNav order
|
||||
var userSideNavStreams = await _unitOfWork.UserRepository.GetSideNavStreams(userId, false);
|
||||
foreach (var sideNavStream in userSideNavStreams.Where(s => s.StreamType == SideNavStreamType.Library))
|
||||
{
|
||||
var library = sideNavStream.Library;
|
||||
feed.Entries.Add(new FeedEntry()
|
||||
{
|
||||
Id = library.Id.ToString(),
|
||||
Title = library.Name,
|
||||
Id = library!.Id.ToString(),
|
||||
Title = library.Name!,
|
||||
Links = new List<FeedLink>()
|
||||
{
|
||||
CreateLink(FeedLinkRelation.SubSection, FeedLinkType.AtomNavigation, $"{prefix}{apiKey}/libraries/{library.Id}"),
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ public class PluginController : BaseApiController
|
|||
/// Authenticate with the Server given an apiKey. This will log you in by returning the user object and the JWT token.
|
||||
/// </summary>
|
||||
/// <remarks>This API is not fully built out and may require more information in later releases</remarks>
|
||||
/// <remarks>This will log unauthorized requests to Security log</remarks>
|
||||
/// <param name="apiKey">API key which will be used to authenticate and return a valid user token back</param>
|
||||
/// <param name="pluginName">Name of the Plugin</param>
|
||||
/// <returns></returns>
|
||||
|
|
@ -37,8 +38,19 @@ public class PluginController : BaseApiController
|
|||
{
|
||||
// NOTE: In order to log information about plugins, we need some Plugin Description information for each request
|
||||
// Should log into access table so we can tell the user
|
||||
var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
var userAgent = HttpContext.Request.Headers["User-Agent"];
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
if (userId <= 0) return Unauthorized();
|
||||
if (userId <= 0)
|
||||
{
|
||||
_logger.LogInformation("A Plugin ({PluginName}) tried to authenticate with an apiKey that doesn't match. Information {Information}", pluginName, new
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
UserAgent = userAgent,
|
||||
ApiKey = apiKey
|
||||
});
|
||||
throw new KavitaUnauthenticatedUserException();
|
||||
}
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
_logger.LogInformation("Plugin {PluginName} has authenticated with {UserName} ({UserId})'s API Key", pluginName, user!.UserName, userId);
|
||||
return new UserDto
|
||||
|
|
@ -54,6 +66,7 @@ public class PluginController : BaseApiController
|
|||
/// <summary>
|
||||
/// Returns the version of the Kavita install
|
||||
/// </summary>
|
||||
/// <remarks>This will log unauthorized requests to Security log</remarks>
|
||||
/// <param name="apiKey">Required for authenticating to get result</param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
|
|
@ -61,7 +74,7 @@ public class PluginController : BaseApiController
|
|||
public async Task<ActionResult<string>> GetVersion([Required] string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
if (userId <= 0) return Unauthorized();
|
||||
if (userId <= 0) throw new KavitaUnauthenticatedUserException();
|
||||
return Ok((await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion)).Value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ public class ScrobblingController : BaseApiController
|
|||
pagination ??= UserParams.Default;
|
||||
var events = await _unitOfWork.ScrobbleRepository.GetUserEvents(User.GetUserId(), filter, pagination);
|
||||
Response.AddPaginationHeader(events.CurrentPage, events.PageSize, events.TotalCount, events.TotalPages);
|
||||
|
||||
return Ok(events);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,4 +183,11 @@ public class StreamController : BaseApiController
|
|||
await _streamService.UpdateSideNavStreamPosition(User.GetUserId(), dto);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost("bulk-sidenav-stream-visibility")]
|
||||
public async Task<ActionResult> BulkUpdateSideNavStream(BulkUpdateSideNavStreamVisibilityDto dto)
|
||||
{
|
||||
await _streamService.UpdateSideNavStreamBulk(User.GetUserId(), dto);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
API/DTOs/SideNav/BulkUpdateSideNavStreamVisibilityDto.cs
Normal file
9
API/DTOs/SideNav/BulkUpdateSideNavStreamVisibilityDto.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs.SideNav;
|
||||
|
||||
public class BulkUpdateSideNavStreamVisibilityDto
|
||||
{
|
||||
public required IList<int> Ids { get; set; }
|
||||
public required bool Visibility { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// v0.7.8.6 explicitly introduced DashboardStream and v0.7.8.9 changed the default seed titles to use locale strings.
|
||||
/// This migration will target nightly releases and should be removed before v0.7.9 release.
|
||||
/// </summary>
|
||||
public static class MigrateDashboardStreamNamesToLocaleKeys
|
||||
{
|
||||
public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger<Program> logger)
|
||||
{
|
||||
var allStreams = await unitOfWork.UserRepository.GetAllDashboardStreams();
|
||||
if (!allStreams.Any(s => s.Name.Equals("On Deck"))) return;
|
||||
|
||||
logger.LogCritical("Running MigrateDashboardStreamNamesToLocaleKeys migration. Please be patient, this may take some time depending on the size of your library. Do not abort, this can break your Database");
|
||||
foreach (var stream in allStreams.Where(s => s.IsProvided))
|
||||
{
|
||||
stream.Name = stream.Name switch
|
||||
{
|
||||
"On Deck" => "on-deck",
|
||||
"Recently Updated" => "recently-updated",
|
||||
"Newly Added" => "newly-added",
|
||||
"More In" => "more-in-genre",
|
||||
_ => stream.Name
|
||||
};
|
||||
unitOfWork.UserRepository.Update(stream);
|
||||
}
|
||||
|
||||
await unitOfWork.CommitAsync();
|
||||
logger.LogInformation("MigrateDashboardStreamNamesToLocaleKeys migration finished");
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +84,7 @@ public interface IUserRepository
|
|||
Task<IList<ScrobbleHoldDto>> GetHolds(int userId);
|
||||
Task<string> GetLocale(int userId);
|
||||
Task<IList<DashboardStreamDto>> GetDashboardStreams(int userId, bool visibleOnly = false);
|
||||
Task<IList<AppUserDashboardStream>> GetAllDashboardStreams();
|
||||
Task<AppUserDashboardStream?> GetDashboardStream(int streamId);
|
||||
Task<IList<AppUserDashboardStream>> GetDashboardStreamWithFilter(int filterId);
|
||||
Task<IList<SideNavStreamDto>> GetSideNavStreams(int userId, bool visibleOnly = false);
|
||||
|
|
@ -91,6 +92,7 @@ public interface IUserRepository
|
|||
Task<IList<AppUserSideNavStream>> GetSideNavStreamWithFilter(int filterId);
|
||||
Task<IList<AppUserSideNavStream>> GetSideNavStreamsByLibraryId(int libraryId);
|
||||
Task<IList<AppUserSideNavStream>> GetSideNavStreamWithExternalSource(int externalSourceId);
|
||||
Task<IList<AppUserSideNavStream>> GetDashboardStreamsByIds(IList<int> streamIds);
|
||||
}
|
||||
|
||||
public class UserRepository : IUserRepository
|
||||
|
|
@ -356,6 +358,13 @@ public class UserRepository : IUserRepository
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<AppUserDashboardStream>> GetAllDashboardStreams()
|
||||
{
|
||||
return await _context.AppUserDashboardStream
|
||||
.OrderBy(d => d.Order)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<AppUserDashboardStream?> GetDashboardStream(int streamId)
|
||||
{
|
||||
return await _context.AppUserDashboardStream
|
||||
|
|
@ -453,6 +462,13 @@ public class UserRepository : IUserRepository
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<AppUserSideNavStream>> GetDashboardStreamsByIds(IList<int> streamIds)
|
||||
{
|
||||
return await _context.AppUserSideNavStream
|
||||
.Where(d => streamIds.Contains(d.Id))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<AppUser>> GetAdminUsersAsync()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@
|
|||
"collections": "All Collections",
|
||||
"browse-collections": "Browse by Collections",
|
||||
"smart-filters": "Smart Filters",
|
||||
"external-sources": "External Sources",
|
||||
"browse-external-sources": "Browse External Sources",
|
||||
"browse-smart-filters": "Browse by Smart Filters",
|
||||
"reading-list-restricted": "Reading list does not exist or you don't have access",
|
||||
"query-required": "You must pass a query parameter",
|
||||
|
|
|
|||
69
API/Middleware/SecurityMiddleware.cs
Normal file
69
API/Middleware/SecurityMiddleware.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using API.Errors;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Serilog;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace API.Middleware;
|
||||
|
||||
public class SecurityEventMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public SecurityEventMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
|
||||
_logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.File(Path.Join(Directory.GetCurrentDirectory(), "config/logs/", "security.log"), rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch (KavitaUnauthenticatedUserException ex)
|
||||
{
|
||||
var ipAddress = context.Connection.RemoteIpAddress?.ToString();
|
||||
var requestMethod = context.Request.Method;
|
||||
var requestPath = context.Request.Path;
|
||||
var userAgent = context.Request.Headers["User-Agent"];
|
||||
var securityEvent = new
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
RequestMethod = requestMethod,
|
||||
RequestPath = requestPath,
|
||||
UserAgent = userAgent,
|
||||
CreatedAt = DateTime.Now,
|
||||
CreatedAtUtc = DateTime.UtcNow,
|
||||
};
|
||||
_logger.Information("Unauthorized User attempting to access API. {@Event}", securityEvent);
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
|
||||
|
||||
const string errorMessage = "Unauthorized";
|
||||
|
||||
var response = new ApiException(context.Response.StatusCode, errorMessage, ex.StackTrace);
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy =
|
||||
JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(response, options);
|
||||
|
||||
await context.Response.WriteAsync(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ public interface IStreamService
|
|||
Task<DashboardStreamDto> CreateDashboardStreamFromSmartFilter(int userId, int smartFilterId);
|
||||
Task UpdateDashboardStream(int userId, DashboardStreamDto dto);
|
||||
Task UpdateDashboardStreamPosition(int userId, UpdateStreamPositionDto dto);
|
||||
Task UpdateSideNavStreamBulk(int userId, BulkUpdateSideNavStreamVisibilityDto dto);
|
||||
Task<SideNavStreamDto> CreateSideNavStreamFromSmartFilter(int userId, int smartFilterId);
|
||||
Task<SideNavStreamDto> CreateSideNavStreamFromExternalSource(int userId, int externalSourceId);
|
||||
Task UpdateSideNavStream(int userId, SideNavStreamDto dto);
|
||||
|
|
@ -31,6 +32,7 @@ public interface IStreamService
|
|||
Task<ExternalSourceDto> CreateExternalSource(int userId, ExternalSourceDto dto);
|
||||
Task<ExternalSourceDto> UpdateExternalSource(int userId, ExternalSourceDto dto);
|
||||
Task DeleteExternalSource(int userId, int externalSourceId);
|
||||
|
||||
}
|
||||
|
||||
public class StreamService : IStreamService
|
||||
|
|
@ -134,6 +136,20 @@ public class StreamService : IStreamService
|
|||
user.Id);
|
||||
}
|
||||
|
||||
public async Task UpdateSideNavStreamBulk(int userId, BulkUpdateSideNavStreamVisibilityDto dto)
|
||||
{
|
||||
var streams = await _unitOfWork.UserRepository.GetDashboardStreamsByIds(dto.Ids);
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
stream.Visible = dto.Visibility;
|
||||
_unitOfWork.UserRepository.Update(stream);
|
||||
}
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
await _eventHub.SendMessageToAsync(MessageFactory.SideNavUpdate, MessageFactory.SideNavUpdateEvent(userId),
|
||||
userId);
|
||||
}
|
||||
|
||||
public async Task<SideNavStreamDto> CreateSideNavStreamFromSmartFilter(int userId, int smartFilterId)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.SideNavStreams);
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ public class Startup
|
|||
|
||||
// v0.7.9
|
||||
await MigrateUserLibrarySideNavStream.Migrate(unitOfWork, dataContext, logger);
|
||||
await MigrateDashboardStreamNamesToLocaleKeys.Migrate(unitOfWork, dataContext, logger);
|
||||
|
||||
// Update the version in the DB after all migrations are run
|
||||
var installVersion = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion);
|
||||
|
|
@ -269,9 +270,9 @@ public class Startup
|
|||
logger.LogCritical(ex, "An error occurred during migration");
|
||||
}
|
||||
|
||||
|
||||
|
||||
app.UseMiddleware<ExceptionMiddleware>();
|
||||
app.UseMiddleware<SecurityEventMiddleware>();
|
||||
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue