Dashboard Customization Polish (#2295)

This commit is contained in:
Joe Milazzo 2023-09-30 12:33:16 -06:00 committed by GitHub
parent 25e759d301
commit 25ffb2ffe1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 255 additions and 258 deletions

View file

@ -55,54 +55,54 @@
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="Docnet.Core" Version="2.4.0-alpha.4" />
<PackageReference Include="EasyCaching.InMemory" Version="1.9.0" />
<PackageReference Include="ExCSS" Version="4.2.1" />
<PackageReference Include="Docnet.Core" Version="2.6.0" />
<PackageReference Include="EasyCaching.InMemory" Version="1.9.1" />
<PackageReference Include="ExCSS" Version="4.2.2" />
<PackageReference Include="Flurl" Version="3.0.7" />
<PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Hangfire" Version="1.8.4" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.4" />
<PackageReference Include="Hangfire" Version="1.8.5" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.5" />
<PackageReference Include="Hangfire.InMemory" Version="0.5.1" />
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.3.4" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.51" />
<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.10" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.10" />
<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.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.11" />
<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.3" />
<PackageReference Include="NetVips.Native" Version="8.14.5" />
<PackageReference Include="NReco.Logging.File" Version="1.1.6" />
<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" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.1" />
<PackageReference Include="Serilog.Sinks.AspNetCore.SignalR" Version="0.4.0" />
<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.33.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.7.0.75501">
<PackageReference Include="SharpCompress" Version="0.34.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.2" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.10.0.77988">
<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.8" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.1" />
<PackageReference Include="System.IO.Abstractions" Version="19.2.51" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.11" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" />
<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" />
</ItemGroup>

View file

@ -193,7 +193,11 @@ public class AccountController : BaseApiController
}
if (user == null) return Unauthorized(await _localizationService.Get("en", "bad-credentials"));
if (user == null)
{
_logger.LogWarning("Attempted login by {UserName} failed due to unable to find account", loginDto.Username);
return Unauthorized(await _localizationService.Get("en", "bad-credentials"));
}
var roles = await _userManager.GetRolesAsync(user);
if (!roles.Contains(PolicyConstants.LoginRole)) return Unauthorized(await _localizationService.Translate(user.Id, "disabled-account"));
@ -205,12 +209,19 @@ public class AccountController : BaseApiController
if (result.IsLockedOut)
{
await _userManager.UpdateSecurityStampAsync(user);
return Unauthorized(await _localizationService.Translate(user.Id, "locked-out"));
var errorStr = await _localizationService.Translate(user.Id, "locked-out");
_logger.LogWarning("{UserName} failed to log in at {Time}: {Issue}", user.UserName, user.LastActive,
errorStr);
return Unauthorized(errorStr);
}
if (!result.Succeeded)
{
return Unauthorized(await _localizationService.Translate(user.Id, result.IsNotAllowed ? "confirm-email" : "bad-credentials"));
var errorStr = await _localizationService.Translate(user.Id,
result.IsNotAllowed ? "confirm-email" : "bad-credentials");
_logger.LogWarning("{UserName} failed to log in at {Time}: {Issue}", user.UserName, user.LastActive,
errorStr);
return Unauthorized(errorStr);
}
}
@ -399,7 +410,6 @@ public class AccountController : BaseApiController
_logger.LogError(ex, "There was an error during invite user flow, unable to send an email");
}
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName!), user.Id);
return Ok();

View file

@ -51,7 +51,6 @@ public interface ILibraryRepository
Task<bool> DoAnySeriesFoldersMatch(IEnumerable<string> folders);
Task<string?> GetLibraryCoverImageAsync(int libraryId);
Task<IList<string>> GetAllCoverImagesAsync();
Task<IDictionary<int, LibraryType>> GetLibraryTypesForIdsAsync(IEnumerable<int> libraryIds);
Task<IList<Library>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
Task<bool> GetAllowsScrobblingBySeriesId(int seriesId);
}
@ -346,28 +345,6 @@ public class LibraryRepository : ILibraryRepository
.ToListAsync())!;
}
public async Task<IDictionary<int, LibraryType>> GetLibraryTypesForIdsAsync(IEnumerable<int> libraryIds)
{
var types = await _context.Library
.Where(l => libraryIds.Contains(l.Id))
.AsNoTracking()
.Select(l => new
{
LibraryId = l.Id,
LibraryType = l.Type
})
.ToListAsync();
var dict = new Dictionary<int, LibraryType>();
foreach (var type in types)
{
dict.TryAdd(type.LibraryId, type.LibraryType);
}
return dict;
}
public async Task<IList<Library>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
{
var extension = encodeFormat.GetExtension();

View file

@ -9,7 +9,6 @@ namespace API.Data.Repositories;
public interface IMangaFileRepository
{
void Update(MangaFile file);
Task<bool> AnyMissingExtension();
Task<IList<MangaFile>> GetAllWithMissingExtension();
}
@ -27,11 +26,6 @@ public class MangaFileRepository : IMangaFileRepository
_context.Entry(file).State = EntityState.Modified;
}
public async Task<bool> AnyMissingExtension()
{
return (await _context.MangaFile.CountAsync(f => string.IsNullOrEmpty(f.Extension))) > 0;
}
public async Task<IList<MangaFile>> GetAllWithMissingExtension()
{
return await _context.MangaFile

View file

@ -15,7 +15,6 @@ public interface IMediaErrorRepository
void Attach(MediaError error);
void Remove(MediaError error);
Task<MediaError> Find(string filename);
Task<PagedList<MediaErrorDto>> GetAllErrorDtosAsync(UserParams userParams);
IEnumerable<MediaErrorDto> GetAllErrorDtosAsync();
Task<bool> ExistsAsync(MediaError error);
Task DeleteAll();
@ -49,15 +48,6 @@ public class MediaErrorRepository : IMediaErrorRepository
return _context.MediaError.Where(e => e.FilePath == filename).SingleOrDefaultAsync();
}
public Task<PagedList<MediaErrorDto>> GetAllErrorDtosAsync(UserParams userParams)
{
var query = _context.MediaError
.OrderByDescending(m => m.Created)
.ProjectTo<MediaErrorDto>(_mapper.ConfigurationProvider)
.AsNoTracking();
return PagedList<MediaErrorDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
}
public IEnumerable<MediaErrorDto> GetAllErrorDtosAsync()
{
var query = _context.MediaError

View file

@ -19,7 +19,7 @@ public interface IPersonRepository
Task<IList<Person>> GetAllPeople();
Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId);
Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role);
Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false);
Task RemoveAllPeopleNoLongerAssociated();
Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(List<int> libraryIds, int userId);
Task<int> GetCountAsync();
@ -46,7 +46,7 @@ public class PersonRepository : IPersonRepository
_context.Person.Remove(person);
}
public async Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false)
public async Task RemoveAllPeopleNoLongerAssociated()
{
var peopleWithNoConnections = await _context.Person
.Include(p => p.SeriesMetadatas)

View file

@ -18,7 +18,7 @@ public interface IScrobbleRepository
void Attach(ScrobbleEvent evt);
void Attach(ScrobbleError error);
void Remove(ScrobbleEvent evt);
void Remove(IList<ScrobbleEvent> evts);
void Remove(IEnumerable<ScrobbleEvent> events);
void Update(ScrobbleEvent evt);
Task<IList<ScrobbleEvent>> GetByEvent(ScrobbleEventType type, bool isProcessed = false);
Task<IList<ScrobbleEvent>> GetProcessedEvents(int daysAgo);
@ -60,9 +60,9 @@ public class ScrobbleRepository : IScrobbleRepository
_context.ScrobbleEvent.Remove(evt);
}
public void Remove(IList<ScrobbleEvent> evts)
public void Remove(IEnumerable<ScrobbleEvent> events)
{
_context.ScrobbleEvent.RemoveRange(evts);
_context.ScrobbleEvent.RemoveRange(events);
}
public void Update(ScrobbleEvent evt)

View file

@ -128,8 +128,6 @@ public interface ISeriesRepository
Task<Series?> GetSeriesByFolderPath(string folder, SeriesIncludes includes = SeriesIncludes.None);
Task<IEnumerable<Series>> GetAllSeriesByNameAsync(IList<string> normalizedNames,
int userId, SeriesIncludes includes = SeriesIncludes.None);
Task<IEnumerable<SeriesDto>> GetAllSeriesDtosByNameAsync(IEnumerable<string> normalizedNames,
int userId, SeriesIncludes includes = SeriesIncludes.None);
Task<Series?> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId, MangaFormat format, bool withFullIncludes = true);
Task<IList<Series>> RemoveSeriesNotInList(IList<ParsedSeries> seenSeries, int libraryId);
Task<IDictionary<string, IList<SeriesModified>>> GetFolderPathMap(int libraryId);
@ -1054,7 +1052,7 @@ public class SeriesRepository : ISeriesRepository
private static IQueryable<Series> BuildFilterGroup(int userId, FilterStatementDto statement, IQueryable<Series> query)
{
var (value, _) = FilterFieldValueConverter.ConvertValue(statement.Field, statement.Value);
var value = FilterFieldValueConverter.ConvertValue(statement.Field, statement.Value);
return statement.Field switch
{
FilterField.Summary => query.HasSummary(true, statement.Comparison, (string) value),
@ -1085,7 +1083,7 @@ public class SeriesRepository : ISeriesRepository
FilterField.WantToRead =>
// This is handled in the higher level of code as it's more general
query,
FilterField.ReadProgress => query.HasReadingProgress(true, statement.Comparison, (int) value, userId),
FilterField.ReadProgress => query.HasReadingProgress(true, statement.Comparison, (float) value, userId),
FilterField.Formats => query.HasFormat(true, statement.Comparison, (IList<MangaFormat>) value),
FilterField.ReleaseYear => query.HasReleaseYear(true, statement.Comparison, (int) value),
FilterField.ReadTime => query.HasAverageReadTime(true, statement.Comparison, (int) value),
@ -1471,20 +1469,6 @@ public class SeriesRepository : ISeriesRepository
.ToListAsync();
}
public async Task<IEnumerable<SeriesDto>> GetAllSeriesDtosByNameAsync(IEnumerable<string> normalizedNames, int userId,
SeriesIncludes includes = SeriesIncludes.None)
{
var libraryIds = _context.Library.GetUserLibraries(userId);
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
return await _context.Series
.Where(s => normalizedNames.Contains(s.NormalizedName))
.Where(s => libraryIds.Contains(s.LibraryId))
.RestrictAgainstAgeRestriction(userRating)
.Includes(includes)
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}
/// <summary>
/// Finds a series by series name or localized name for a given library.

View file

@ -19,7 +19,6 @@ public interface ISiteThemeRepository
Task<SiteThemeDto?> GetThemeDtoByName(string themeName);
Task<SiteTheme> GetDefaultTheme();
Task<IEnumerable<SiteTheme>> GetThemes();
Task<SiteTheme?> GetThemeById(int themeId);
}
public class SiteThemeRepository : ISiteThemeRepository
@ -89,13 +88,6 @@ public class SiteThemeRepository : ISiteThemeRepository
.ToListAsync();
}
public async Task<SiteTheme?> GetThemeById(int themeId)
{
return await _context.SiteTheme
.Where(t => t.Id == themeId)
.SingleOrDefaultAsync();
}
public async Task<SiteThemeDto?> GetThemeDto(int themeId)
{
return await _context.SiteTheme

View file

@ -17,7 +17,7 @@ public interface ITagRepository
void Remove(Tag tag);
Task<IList<Tag>> GetAllTagsAsync();
Task<IList<TagDto>> GetAllTagDtosAsync(int userId);
Task RemoveAllTagNoLongerAssociated(bool removeExternal = false);
Task RemoveAllTagNoLongerAssociated();
Task<IList<TagDto>> GetAllTagDtosForLibrariesAsync(IList<int> libraryIds, int userId);
}
@ -42,7 +42,7 @@ public class TagRepository : ITagRepository
_context.Tag.Remove(tag);
}
public async Task RemoveAllTagNoLongerAssociated(bool removeExternal = false)
public async Task RemoveAllTagNoLongerAssociated()
{
var tagsWithNoConnections = await _context.Tag
.Include(p => p.SeriesMetadatas)

View file

@ -233,7 +233,7 @@ public static class SeriesFilter
/// <exception cref="KavitaException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static IQueryable<Series> HasReadingProgress(this IQueryable<Series> queryable, bool condition,
FilterComparison comparison, int readProgress, int userId)
FilterComparison comparison, float readProgress, int userId)
{
if (!condition) return queryable;

View file

@ -8,72 +8,72 @@ namespace API.Helpers.Converters;
public static class FilterFieldValueConverter
{
public static (object Value, Type Type) ConvertValue(FilterField field, string value)
public static object ConvertValue(FilterField field, string value)
{
return field switch
{
FilterField.SeriesName => (value, typeof(string)),
FilterField.Path => (value, typeof(string)),
FilterField.FilePath => (value, typeof(string)),
FilterField.ReleaseYear => (int.Parse(value), typeof(int)),
FilterField.Languages => (value.Split(',').ToList(), typeof(IList<string>)),
FilterField.PublicationStatus => (value.Split(',')
FilterField.SeriesName => value,
FilterField.Path => value,
FilterField.FilePath => value,
FilterField.ReleaseYear => int.Parse(value),
FilterField.Languages => value.Split(',').ToList(),
FilterField.PublicationStatus => value.Split(',')
.Select(x => (PublicationStatus) Enum.Parse(typeof(PublicationStatus), x))
.ToList(), typeof(IList<PublicationStatus>)),
FilterField.Summary => (value, typeof(string)),
FilterField.AgeRating => (value.Split(',')
.ToList(),
FilterField.Summary => value,
FilterField.AgeRating => value.Split(',')
.Select(x => (AgeRating) Enum.Parse(typeof(AgeRating), x))
.ToList(), typeof(IList<AgeRating>)),
FilterField.UserRating => (int.Parse(value), typeof(int)),
FilterField.Tags => (value.Split(',')
.ToList(),
FilterField.UserRating => int.Parse(value),
FilterField.Tags => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.CollectionTags => (value.Split(',')
.ToList(),
FilterField.CollectionTags => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Translators => (value.Split(',')
.ToList(),
FilterField.Translators => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Characters => (value.Split(',')
.ToList(),
FilterField.Characters => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Publisher => (value.Split(',')
.ToList(),
FilterField.Publisher => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Editor => (value.Split(',')
.ToList(),
FilterField.Editor => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.CoverArtist => (value.Split(',')
.ToList(),
FilterField.CoverArtist => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Letterer => (value.Split(',')
.ToList(),
FilterField.Letterer => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Colorist => (value.Split(',')
.ToList(),
FilterField.Colorist => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Inker => (value.Split(',')
.ToList(),
FilterField.Inker => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Penciller => (value.Split(',')
.ToList(),
FilterField.Penciller => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Writers => (value.Split(',')
.ToList(),
FilterField.Writers => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Genres => (value.Split(',')
.ToList(),
FilterField.Genres => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.Libraries => (value.Split(',')
.ToList(),
FilterField.Libraries => value.Split(',')
.Select(int.Parse)
.ToList(), typeof(IList<int>)),
FilterField.WantToRead => (bool.Parse(value), typeof(bool)),
FilterField.ReadProgress => (int.Parse(value), typeof(int)),
FilterField.ReadingDate => (DateTime.Parse(value), typeof(DateTime?)),
FilterField.Formats => (value.Split(',')
.ToList(),
FilterField.WantToRead => bool.Parse(value),
FilterField.ReadProgress => float.Parse(value),
FilterField.ReadingDate => DateTime.Parse(value),
FilterField.Formats => value.Split(',')
.Select(x => (MangaFormat) Enum.Parse(typeof(MangaFormat), x))
.ToList(), typeof(IList<MangaFormat>)),
FilterField.ReadTime => (int.Parse(value), typeof(int)),
.ToList(),
FilterField.ReadTime => int.Parse(value),
_ => throw new ArgumentException("Invalid field type")
};
}

View file

@ -85,7 +85,7 @@ public class DirectoryService : IDirectoryService
private const RegexOptions MatchOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase;
private static readonly Regex ExcludeDirectories = new Regex(
@"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle|\.@__thumb|\.caltrash",
@"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle|\.@__thumb|\.caltrash|#recycle",
MatchOptions,
Tasks.Scanner.Parser.Parser.RegexTimeout);
private static readonly Regex FileCopyAppend = new Regex(@"\(\d+\)",

View file

@ -550,9 +550,10 @@ public static class Parser
new Regex(
@"(Глава|глава|Главы|Глава)(\.?)(\s|_)?(?<Chapter>\d+(?:.\d+|-\d+)?)",
MatchOptions, RegexTimeout),
// Hinowa ga CRUSH! 018 (2019) (Digital) (LuCaZ).cbz, Hinowa ga CRUSH! 018.5 (2019) (Digital) (LuCaZ).cbz
new Regex(
@"^(?!Vol)(?<Series>.+?)(?<!Vol)(?<!Vol.)\s(\d\s)?(?<Chapter>\d+(?:\.\d+|-\d+)?)(?:\s\(\d{4}\))?(\b|_|-)",
@"^(?<Series>.+?)(?<!Vol)(?<!Vol.)(?<!Volume)\s(\d\s)?(?<Chapter>\d+(?:\.\d+|-\d+)?)(?:\s\(\d{4}\))?(\b|_|-)",
MatchOptions, RegexTimeout),
// Tower Of God S01 014 (CBT) (digital).cbz
new Regex(
@ -997,6 +998,7 @@ public static class Parser
{
return path.Contains("__MACOSX") || path.StartsWith("@Recently-Snapshot") || path.StartsWith("@recycle")
|| path.StartsWith("._") || Path.GetFileName(path).StartsWith("._") || path.Contains(".qpkg")
|| path.StartsWith("#recycle")
|| path.Contains(".caltrash");
}

View file

@ -1,5 +1,6 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using API.Data;
using API.SignalR.Presence;
@ -55,8 +56,7 @@ public class EventHub : IEventHub
/// <returns></returns>
public async Task SendMessageToAsync(string method, SignalRMessage message, int userId)
{
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId) ?? throw new InvalidOperationException();
await _messageHub.Clients.User(user.UserName!).SendAsync(method, message);
await _messageHub.Clients.Users(new List<string>() {userId + string.Empty}).SendAsync(method, message);
}
}

View file

@ -22,7 +22,8 @@ public class MessageHub : Hub
public override async Task OnConnectedAsync()
{
await _tracker.UserConnected(Context.User!.GetUserId(), Context.ConnectionId);
var userId = Context.User!.GetUserId();
await _tracker.UserConnected(userId, Context.ConnectionId);
var currentUsers = await PresenceTracker.GetOnlineUsers();
await Clients.All.SendAsync(MessageFactory.OnlineUsers, currentUsers);

View file

@ -80,7 +80,10 @@ public class PresenceTracker : IPresenceTracker
string[] onlineUsers;
lock (OnlineUsers)
{
onlineUsers = OnlineUsers.OrderBy(k => k.Value.UserName).Select(k => k.Value.UserName).ToArray();
onlineUsers = OnlineUsers
.Select(k => k.Value.UserName)
.Order()
.ToArray();
}
return Task.FromResult(onlineUsers);
@ -91,7 +94,10 @@ public class PresenceTracker : IPresenceTracker
int[] onlineUsers;
lock (OnlineUsers)
{
onlineUsers = OnlineUsers.Where(pair => pair.Value.IsAdmin).OrderBy(k => k.Key).Select(k => k.Key).ToArray();
onlineUsers = OnlineUsers.Where(pair => pair.Value.IsAdmin)
.Select(k => k.Key)
.Order()
.ToArray();
}