Rework depot results, API docs via Scalar

This commit is contained in:
Pat Hartl 2024-12-16 00:57:11 -06:00
parent 37ba817706
commit ce17b7999f
12 changed files with 130 additions and 113 deletions

View file

@ -9,6 +9,7 @@ namespace LANCommander.Launcher.Models
public GroupBy GroupBy { get; set; } = GroupBy.None;
public SortBy SortBy { get; set; } = SortBy.Title;
public SortDirection SortDirection { get; set; } = SortDirection.Ascending;
public ICollection<Collection> Collections { get; set; }
public ICollection<Engine> Engines { get; set; }
public ICollection<Genre> Genres { get; set; }
public ICollection<Tag> Tags { get; set; }

View file

@ -44,11 +44,11 @@ namespace LANCommander.Launcher.Services
using (var op = Logger.BeginOperation(LogLevel.Trace, "Loading depot items from host"))
{
var games = await Client.Depot.GetGamesAsync();
var results = await Client.Depot.GetAsync();
Filter.Populate(games);
Filter.Populate(results);
foreach (var item in games.Select(g => new ListItem(g)).OrderByTitle(g => !String.IsNullOrWhiteSpace(g.SortName) ? g.SortName : g.Name))
foreach (var item in results.Games.Select(g => new ListItem(g)).OrderByTitle(g => !String.IsNullOrWhiteSpace(g.SortName) ? g.SortName : g.Name))
{
Items.Add(item);
}

View file

@ -25,53 +25,21 @@ namespace LANCommander.Launcher.Models
public Func<ListItem, string[]> GroupSelector { get; set; } = _ => new string[] { };
public void Populate(IEnumerable<SDK.Models.DepotGame> games)
public void Populate(DepotResults results)
{
var multiplayerModes = games.Where(g => g.MultiplayerModes != null).SelectMany(g => g.MultiplayerModes);
var multiplayerModes = results.Games
.Where(g => g.MultiplayerModes != null)
.SelectMany(g => g.MultiplayerModes);
DataSource.Engines = games
.Select(i => i.Engine)
.Where(e => e != null)
.DistinctBy(e => e.Id)
.OrderBy(e => e.Name)
.ToList();
DataSource.Collections = results.Collections.OrderBy(c => c.Name).ToList();
DataSource.Engines = results.Engines.OrderBy(e => e.Name).ToList();
DataSource.Genres = results.Genres.OrderBy(g => g.Name).ToList();
DataSource.Tags = results.Tags.OrderBy(t => t.Name).ToList();
DataSource.Platforms = results.Platforms.OrderBy(p => p.Name).ToList();
DataSource.Developers = results.Companies.OrderBy(c => c.Name).ToList();
DataSource.Publishers = results.Companies.OrderBy(c => c.Name).ToList();
DataSource.Genres = games
.SelectMany(i => i.Genres)
.Where(g => g != null)
.DistinctBy(g => g.Id)
.OrderBy(g => g.Name)
.ToList();
DataSource.Tags = games
.SelectMany(i => i.Tags)
.Where(t => t != null)
.DistinctBy(t => t.Id)
.OrderBy(t => t.Name)
.ToList();
DataSource.Platforms = games
.SelectMany(i => i.Platforms)
.Where(p => p != null)
.DistinctBy(p => p.Id)
.OrderBy(p => p.Name)
.ToList();
DataSource.Developers = games
.SelectMany(i => i.Developers)
.Where(c => c != null)
.DistinctBy(c => c.Id)
.OrderBy(c => c.Name)
.ToList();
DataSource.Publishers = games
.SelectMany(i => i.Publishers)
.Where(c => c != null)
.DistinctBy(c => c.Id)
.OrderBy(c => c.Name)
.ToList();
if (games.Any(li => li.Singleplayer))
if (results.Games.Any(li => li.Singleplayer))
DataSource.MinPlayers = 1;
else if (multiplayerModes.Any())
DataSource.MinPlayers = multiplayerModes.Where(i => i != null).Min(i => i.MinPlayers);
@ -88,19 +56,25 @@ namespace LANCommander.Launcher.Models
items = items.Where(i => i.Name?.IndexOf(SelectedOptions.Title, StringComparison.OrdinalIgnoreCase) >= 0 || i.SortName?.IndexOf(SelectedOptions.Title, StringComparison.OrdinalIgnoreCase) >= 0);
if (SelectedOptions.Engines != null && SelectedOptions.Engines.Any())
items = items.Where(i => SelectedOptions.Engines.Any(e => e.Id == (i.DataItem as DepotGame)?.Engine?.Id));
items = items.Where(i => SelectedOptions.Engines.Any(e => (i.DataItem as DepotGame).EngineId == e.Id));
if (SelectedOptions.Collections != null && SelectedOptions.Collections.Any())
items = items.Where(i => SelectedOptions.Collections.Any(fc => (i.DataItem as DepotGame).Collections.Contains(fc.Id)));
if (SelectedOptions.Genres != null && SelectedOptions.Genres.Any())
items = items.Where(i => SelectedOptions.Genres.Any(fg => (i.DataItem as DepotGame).Genres.Any(g => fg.Id == g.Id)));
items = items.Where(i => SelectedOptions.Genres.Any(fg => (i.DataItem as DepotGame).Genres.Contains(fg.Id)));
if (SelectedOptions.Tags != null && SelectedOptions.Tags.Any())
items = items.Where(i => SelectedOptions.Tags.Any(ft => (i.DataItem as DepotGame).Tags.Any(t => ft.Id == t.Id)));
items = items.Where(i => SelectedOptions.Tags.Any(ft => (i.DataItem as DepotGame).Tags.Contains(ft.Id)));
if (SelectedOptions.Developers != null && SelectedOptions.Developers.Any())
items = items.Where(i => SelectedOptions.Developers.Any(fc => (i.DataItem as DepotGame).Developers.Any(c => fc.Id == c.Id)));
items = items.Where(i => SelectedOptions.Developers.Any(fc => (i.DataItem as DepotGame).Developers.Contains(fc.Id)));
if (SelectedOptions.Publishers != null && SelectedOptions.Publishers.Any())
items = items.Where(i => SelectedOptions.Publishers.Any(fc => (i.DataItem as DepotGame).Publishers.Any(c => fc.Id == c.Id)));
items = items.Where(i => SelectedOptions.Publishers.Any(fc => (i.DataItem as DepotGame).Publishers.Contains(fc.Id)));
if (SelectedOptions.Platforms != null && SelectedOptions.Platforms.Any())
items = items.Where(i => SelectedOptions.Platforms.Any(fp => (i.DataItem as DepotGame).Platforms.Contains(fp.Id)));
if (SelectedOptions.MinPlayers != null)
items = items.Where(i => (i.DataItem as DepotGame).MultiplayerModes.Any(mm => mm.MinPlayers <= SelectedOptions.MinPlayers && mm.MaxPlayers >= SelectedOptions.MinPlayers));
@ -137,25 +111,25 @@ namespace LANCommander.Launcher.Models
case Enums.GroupBy.Collection:
GroupSelector = (g) =>
(g.DataItem as DepotGame)?.Collections?
.Where(c => c?.Name != null)
DataSource.Collections
.Where(c => (g.DataItem as DepotGame).Collections.Contains(c.Id))
.Select(c => c.Name)
.ToArray() ?? Array.Empty<string>();
break;
case Enums.GroupBy.Genre:
GroupSelector = (g) =>
(g.DataItem as DepotGame)?.Genres?
.Where(ge => ge?.Name != null)
.Select(ge => ge.Name)
DataSource.Genres
.Where(ge => (g.DataItem as DepotGame).Genres.Contains(ge.Id))
.Select(g => g.Name)
.ToArray() ?? Array.Empty<string>();
break;
case Enums.GroupBy.Platform:
GroupSelector = (g) =>
(g.DataItem as DepotGame)?.Platforms?
.Where(p => p?.Name != null)
.Select(p => p.Name)
DataSource.Platforms
.Where(p => (g.DataItem as DepotGame).Platforms.Contains(p.Id))
.Select(g => g.Name)
.ToArray() ?? Array.Empty<string>();
break;
}

View file

@ -33,6 +33,9 @@
<FormItem Label="Engine">
<TagsInput Entities="context.DataSource.Engines" @bind-Values="@context.SelectedOptions.Engines" OptionLabelSelector="e => e.Name" TItem="Engine" />
</FormItem>
<FormItem Label="Collection">
<TagsInput Entities="context.DataSource.Collections" @bind-Values="@context.SelectedOptions.Collections" OptionLabelSelector="c => c.Name" TItem="Collection" />
</FormItem>
<FormItem Label="Genre">
<TagsInput Entities="context.DataSource.Genres" @bind-Values="@context.SelectedOptions.Genres" OptionLabelSelector="g => g.Name" TItem="Genre" />
</FormItem>

View file

@ -21,13 +21,13 @@ namespace LANCommander.SDK.Models
public bool InLibrary { get; set; }
public GameType Type { get; set; }
public Media Cover { get; set; }
public IEnumerable<Collection> Collections { get; set; }
public IEnumerable<Company> Developers { get; set; }
public IEnumerable<Company> Publishers { get; set; }
public Engine Engine { get; set; }
public IEnumerable<Genre> Genres { get; set; }
public IEnumerable<Guid> Collections { get; set; }
public IEnumerable<Guid> Developers { get; set; }
public IEnumerable<Guid> Publishers { get; set; }
public Guid EngineId { get; set; }
public IEnumerable<Guid> Genres { get; set; }
public IEnumerable<MultiplayerMode> MultiplayerModes { get; set; }
public IEnumerable<Platform> Platforms { get; set; }
public IEnumerable<Tag> Tags { get; set; }
public IEnumerable<Guid> Platforms { get; set; }
public IEnumerable<Guid> Tags { get; set; }
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.SDK.Models
{
public class DepotResults
{
public ICollection<DepotGame> Games { get; set; }
public ICollection<Collection> Collections { get; set; }
public ICollection<Company> Companies { get; set; }
public ICollection<Engine> Engines { get; set; }
public ICollection<Genre> Genres { get; set; }
public ICollection<Platform> Platforms { get; set; }
public ICollection<Tag> Tags { get; set; }
}
}

View file

@ -27,9 +27,9 @@ namespace LANCommander.SDK.Services
Logger = logger;
}
public async Task<IEnumerable<DepotGame>> GetGamesAsync()
public async Task<DepotResults> GetAsync()
{
return await Client.GetRequestAsync<IEnumerable<DepotGame>>("/api/Depot/Games");
return await Client.GetRequestAsync<DepotResults>("/api/Depot");
}
public async Task<DepotGame> GetGameAsync(Guid gameId)

View file

@ -29,7 +29,7 @@ namespace LANCommander.Server.Services
public async Task<Library> GetByUserIdAsync(Guid userId)
{
var library = await Include(l => l.Games.Select(g => g.Id)).FirstOrDefaultAsync(l => l.User.Id == userId);
var library = await Include(l => l.Games).FirstOrDefaultAsync(l => l.User.Id == userId);
if (library == null)
{

View file

@ -49,7 +49,25 @@ namespace LANCommander.Server
CreateMap<Data.Models.Game, SDK.Models.DepotGame>()
.ForMember(dest =>
dest.Cover,
opt => opt.MapFrom(src => src.Media.Where(m => m.Type == SDK.Enums.MediaType.Cover).FirstOrDefault()));
opt => opt.MapFrom(src => src.Media.Where(m => m.Type == SDK.Enums.MediaType.Cover).FirstOrDefault()))
.ForMember(dest =>
dest.Collections,
opt => opt.MapFrom(src => src.Tags.Select(c => c.Id)))
.ForMember(dest =>
dest.Developers,
opt => opt.MapFrom(src => src.Developers.Select(c => c.Id)))
.ForMember(dest =>
dest.Publishers,
opt => opt.MapFrom(src => src.Publishers.Select(c => c.Id)))
.ForMember(dest =>
dest.Genres,
opt => opt.MapFrom(src => src.Genres.Select(g => g.Id)))
.ForMember(dest =>
dest.Platforms,
opt => opt.MapFrom(src => src.Platforms.Select(p => p.Id)))
.ForMember(dest =>
dest.Tags,
opt => opt.MapFrom(src => src.Tags.Select(t => t.Id)));
}
}
}

View file

@ -20,69 +20,66 @@ namespace LANCommander.Server.Controllers.Api
private readonly IMapper Mapper;
private readonly IFusionCache Cache;
private readonly GameService GameService;
private readonly CollectionService CollectionService;
private readonly CompanyService CompanyService;
private readonly EngineService EngineService;
private readonly GenreService GenreService;
private readonly PlatformService PlatformService;
private readonly TagService TagService;
private readonly LibraryService LibraryService;
private readonly UserService UserService;
private readonly DatabaseContext DatabaseContext;
public DepotController(
ILogger<DepotController> logger,
IMapper mapper,
IFusionCache cache,
GameService gameService,
CollectionService collectionService,
CompanyService companyService,
EngineService engineService,
GenreService genreService,
PlatformService platformService,
TagService tagService,
LibraryService libraryService,
UserService userService) : base(logger)
UserService userService,
DatabaseContext databaseContext) : base(logger)
{
Mapper = mapper;
Cache = cache;
GameService = gameService;
CollectionService = collectionService;
CompanyService = companyService;
EngineService = engineService;
GenreService = genreService;
PlatformService = platformService;
TagService = tagService;
LibraryService = libraryService;
UserService = userService;
DatabaseContext = databaseContext;
}
[HttpGet("Games")]
public async Task<IEnumerable<SDK.Models.DepotGame>> GetGamesAsync()
[HttpGet]
public async Task<SDK.Models.DepotResults> GetAsync()
{
var user = await UserService.GetAsync(User?.Identity?.Name);
var library = await LibraryService.GetByUserIdAsync(user.Id);
var libraryGameIds = library.Games.Where(g => g != null).Select(g => g.Id).ToList();
var games = await Cache.GetOrSetAsync("DepotGames", async _ =>
var results = await Cache.GetOrSetAsync("DepotResults", async _ =>
{
return await GameService
.Include(g => g.Media.Where(m => m.Type == SDK.Enums.MediaType.Cover))
.Include(g => g.Collections.Select(c => c.Id))
.Include(g => g.Developers.Select(c => c.Id))
.Include(g => g.Genres.Select(g => g.Id))
.Include(g => g.MultiplayerModes)
.Include(g => g.Platforms.Select(p => p.Id))
.Include(g => g.Publishers.Select(c => c.Id))
.Include(g => g.Tags.Select(t => t.Id))
.GetAsync<SDK.Models.DepotGame>();
}, TimeSpan.MaxValue);
var accessibleGames = games.ToList();
if (Settings.Roles.RestrictGamesByCollection && !User.IsInRole(RoleService.AdministratorRoleName))
{
var roles = await UserService.GetRolesAsync(User?.Identity.Name);
var accessibleCollectionIds = roles.SelectMany(r => r.Collections.Select(c => c.Id)).Distinct();
accessibleGames = games.Where(g => g.Collections.Any(c => accessibleCollectionIds.Contains(c.Id))).ToList();
foreach (var game in accessibleGames)
return new SDK.Models.DepotResults
{
game.Collections = game.Collections.Where(c => accessibleCollectionIds.Contains(c.Id));
}
}
Games = await GameService.GetAsync<DepotGame>(),
Collections = await CollectionService.GetAsync<SDK.Models.Collection>(),
Companies = await CompanyService.GetAsync<SDK.Models.Company>(),
Engines = await EngineService.GetAsync<SDK.Models.Engine>(),
Genres = await GenreService.GetAsync<SDK.Models.Genre>(),
Platforms = await PlatformService.GetAsync<SDK.Models.Platform>(),
Tags = await TagService.GetAsync<SDK.Models.Tag>(),
};
});
foreach (var game in accessibleGames)
{
if (libraryGameIds.Contains(game.Id))
game.InLibrary = true;
}
return accessibleGames;
return results;
}
[HttpGet("Games/{id}")]

View file

@ -106,6 +106,7 @@
<PackageReference Include="Razor.Templating.Core" Version="2.0.0" />
<PackageReference Include="RegParserDotNet" Version="1.1.1" />
<PackageReference Include="rix0rrr.BeaconLib" Version="1.0.2" />
<PackageReference Include="Scalar.AspNetCore" Version="1.2.56" />
<PackageReference Include="Semver" Version="2.3.0" />
<PackageReference Include="Serilog" Version="4.0.2" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />

View file

@ -23,6 +23,7 @@ using LANCommander.Server.Jobs.Background;
using Microsoft.Data.Sqlite;
using Microsoft.OpenApi.Models;
using Microsoft.CodeAnalysis.Options;
using Scalar.AspNetCore;
namespace LANCommander.Server
{
@ -234,7 +235,7 @@ namespace LANCommander.Server
builder.Services.AddSwaggerGen(options =>
{
options.CustomSchemaIds(type => type.ToString());
options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Enter a valid access token",
@ -363,8 +364,11 @@ namespace LANCommander.Server
app.UseHsts();
}
app.UseSwagger();
app.UseSwaggerUI();
app.UseSwagger(options =>
{
options.RouteTemplate = "/openapi/{documentName}.json";
});
app.MapScalarApiReference();
app.UseHangfireDashboard();
// app.UseHttpsRedirection();