Refactor Settings

Settings for the server are now combined with the settings from the SDK. This introduces a large breaking change and a migration should be created. This refactor utilizes .NET Configuration and the Options pattern. This will allow for the overriding of any setting using envionment variables. It also means that Settings.yml can be used to override any .NET configuration. A SettingsProvider implementation was created, and any updating of settings was changed from SettingsService.SaveSettings() to SettingsProvider.Update(s => { ... })
This commit is contained in:
Pat Hartl 2025-11-16 12:31:25 -06:00
parent f20d2861bb
commit add770b227
188 changed files with 1271 additions and 919 deletions

View file

@ -15,6 +15,8 @@ using Settings = LANCommander.SDK.Models.Settings;
public class SettingsProvider<TSettings> : ISettingsProvider
where TSettings : Settings, new()
{
public const string FileName = "Settings.yaml";
private readonly string _filePath;
private readonly IOptionsMonitor<TSettings> _optionsMonitor;

View file

@ -12,8 +12,6 @@ namespace LANCommander.Migrations
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
var settings = SettingService.GetSettings();
// Migrate any old icons from the filesystem
if (Directory.Exists("Icon"))
foreach (var file in Directory.EnumerateFiles("Icon"))

View file

@ -12,8 +12,6 @@ namespace LANCommander.Migrations
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
var settings = SettingService.GetSettings();
if (Directory.Exists("Media"))
{
var files = Directory.EnumerateFiles("Media");

View file

@ -13,8 +13,6 @@ namespace LANCommander.Migrations
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
var settings = SettingService.GetSettings();
migrationBuilder.AddColumn<Guid>(
name: "StorageLocationId",
table: "Media",

View file

@ -1,7 +1,7 @@
using LANCommander.Server.Data.Enums;
using LANCommander.Server.Data.Interceptors;
using LANCommander.Server.Data.Models;
using Microsoft.AspNetCore.Identity;
using LANCommander.Server.Settings.Enums;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

View file

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Server.Data.Enums
{
public enum DatabaseProvider
{
Unknown,
SQLite,
MySQL,
PostgreSQL
}
}

View file

@ -1,7 +0,0 @@
namespace LANCommander.Server.Data.Enums;
public enum ServerEngine
{
Local,
Docker
}

View file

@ -18,6 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj" />
<ProjectReference Include="..\LANCommander.Server.Settings\LANCommander.Server.Settings.csproj" />
</ItemGroup>
</Project>

View file

@ -2,7 +2,7 @@
using LANCommander.SDK.Enums;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using LANCommander.Server.Data.Enums;
using LANCommander.Server.Settings.Enums;
namespace LANCommander.Server.Data.Models
{

View file

@ -1,4 +1,5 @@
using LANCommander.Server.Services.Models;
using LANCommander.Server.Settings.Enums;
using Octokit;
using Semver;

View file

@ -1,4 +1,5 @@
using LANCommander.Server.Services.Models;
using LANCommander.Server.Settings.Enums;
using Semver;
namespace LANCommander.Server.Services.Abstractions;

View file

@ -11,10 +11,11 @@ namespace LANCommander.Server.Services
{
public sealed class ActionService(
ILogger<ActionService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Action>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Action>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Action> AddAsync(Action entity)
{

View file

@ -21,11 +21,12 @@ namespace LANCommander.Server.Services
{
public sealed class ArchiveClient(
ILogger<ArchiveClient> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> dbContextFactory,
StorageLocationService storageLocationService) : BaseDatabaseService<Archive>(logger, cache, mapper, httpContextAccessor, dbContextFactory), IArchiveClient
StorageLocationService storageLocationService) : BaseDatabaseService<Archive>(logger, settingsProvider, cache, mapper, httpContextAccessor, dbContextFactory), IArchiveClient
{
public async Task<Archive> GetLatestArchive(Expression<Func<Archive, bool>> predicate)
{

View file

@ -17,11 +17,12 @@ namespace LANCommander.Server.Services
public class AuthenticationService(
ILogger<AuthenticationService> logger,
IMapper mapper,
SettingsProvider<Settings.Settings> settingsProvider,
UserService userService,
RoleService roleService,
ScriptService scriptService,
ITokenProvider tokenProvider,
SDK.Services.ScriptClient scriptClient) : BaseService(logger)
SDK.Services.ScriptClient scriptClient) : BaseService(logger, settingsProvider)
{
public async Task<AuthToken> LoginAsync(string userName, string password)
{
@ -62,7 +63,7 @@ namespace LANCommander.Server.Services
_logger?.LogDebug("Password check for user {UserName} was successful", user.UserName);
if (_settings.Authentication.RequireApproval && !user.Approved && !await userService.IsInRoleAsync(user, RoleService.AdministratorRoleName))
if (_settingsProvider.CurrentValue.Server.Authentication.RequireApproval && !user.Approved && !await userService.IsInRoleAsync(user, RoleService.AdministratorRoleName))
throw new UserAuthenticationException("Account must be approved by an administrator");
var userRoles = await userService.GetRolesAsync(user);
@ -85,7 +86,7 @@ namespace LANCommander.Server.Services
var refreshToken = GenerateRefreshToken();
user.RefreshToken = refreshToken;
user.RefreshTokenExpiration = DateTime.UtcNow.AddDays(_settings.Authentication.TokenLifetime);
user.RefreshTokenExpiration = DateTime.UtcNow.AddDays(_settingsProvider.CurrentValue.Server.Authentication.TokenLifetime);
await userService.UpdateAsync(user);
@ -171,9 +172,9 @@ namespace LANCommander.Server.Services
try
{
if (_settings.Roles.DefaultRoleId == Guid.Empty)
if (_settingsProvider.CurrentValue.Server.Roles.DefaultRoleId == Guid.Empty)
{
var defaultRole = await roleService.GetAsync(_settings.Roles.DefaultRoleId);
var defaultRole = await roleService.GetAsync(_settingsProvider.CurrentValue.Server.Roles.DefaultRoleId);
if (defaultRole != null)
await userService.AddToRoleAsync(user.UserName, defaultRole.Name);
@ -221,7 +222,7 @@ namespace LANCommander.Server.Services
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.Authentication.TokenSecret)),
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settingsProvider.CurrentValue.Server.Authentication.TokenSecret)),
ValidateLifetime = false
};
@ -247,10 +248,10 @@ namespace LANCommander.Server.Services
private JwtSecurityToken GetToken(List<Claim> authClaims)
{
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.Authentication.TokenSecret));
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settingsProvider.CurrentValue.Server.Authentication.TokenSecret));
var token = new JwtSecurityToken(
expires: DateTime.UtcNow.AddDays(_settings.Authentication.TokenLifetime),
expires: DateTime.UtcNow.AddDays(_settingsProvider.CurrentValue.Server.Authentication.TokenLifetime),
claims: authClaims,
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
@ -258,11 +259,11 @@ namespace LANCommander.Server.Services
return token;
}
public static async Task<List<Models.AuthenticationProvider>> GetAuthenticationProviderTemplatesAsync()
public static async Task<List<Settings.Models.AuthenticationProvider>> GetAuthenticationProviderTemplatesAsync()
{
var files = Directory.GetFiles(@"Templates/AuthenticationProviders", "*.yml", SearchOption.AllDirectories);
var externalProviders = new List<Models.AuthenticationProvider>();
var externalProviders = new List<Settings.Models.AuthenticationProvider>();
var deserializer = new DeserializerBuilder()
@ -275,7 +276,7 @@ namespace LANCommander.Server.Services
{
var contents = await File.ReadAllTextAsync(file);
externalProviders.Add(deserializer.Deserialize<Models.AuthenticationProvider>(contents));
externalProviders.Add(deserializer.Deserialize<Settings.Models.AuthenticationProvider>(contents));
}
catch { }
}

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class CategoryService(
ILogger<CategoryService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> context) : BaseDatabaseService<Category>(logger, cache, mapper, httpContextAccessor, context)
IDbContextFactory<DatabaseContext> context) : BaseDatabaseService<Category>(logger, settingsProvider, cache, mapper, httpContextAccessor, context)
{
public override async Task<Category> AddAsync(Category entity)
{

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class ChatMessageService(
ILogger<ChatMessageService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> context) : BaseDatabaseService<ChatMessage>(logger, cache, mapper, httpContextAccessor, context)
IDbContextFactory<DatabaseContext> context) : BaseDatabaseService<ChatMessage>(logger, settingsProvider, cache, mapper, httpContextAccessor, context)
{
public override async Task<ChatMessage> AddAsync(ChatMessage entity)
{

View file

@ -8,11 +8,12 @@ namespace LANCommander.Server.Services
{
public sealed class ChatService(
ILogger<ChatService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
ChatMessageService chatMessageService,
ChatThreadService chatThreadService,
ChatThreadReadStatusService chatThreadReadStatusService,
UserService userService) : BaseService(logger)
UserService userService) : BaseService(logger, settingsProvider)
{
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();
private readonly int _maxCachedMessages = 200;

View file

@ -1,11 +1,6 @@
using AutoMapper;
using LANCommander.Server.Data;
using LANCommander.Server.Data;
using LANCommander.Server.Data.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.Logging;
using ZiggyCreatures.Caching.Fusion;
namespace LANCommander.Server.Services
{

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class ChatThreadService(
ILogger<ChatThreadService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> context) : BaseDatabaseService<ChatThread>(logger, cache, mapper, httpContextAccessor, context)
IDbContextFactory<DatabaseContext> context) : BaseDatabaseService<ChatThread>(logger, settingsProvider, cache, mapper, httpContextAccessor, context)
{
public override async Task<ChatThread> AddAsync(ChatThread entity)
{

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class CollectionService(
ILogger<CollectionService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Collection>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Collection>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Collection> AddAsync(Collection entity)
{

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class CompanyService(
ILogger<CompanyService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Company>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Company>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Company> AddAsync(Company entity)
{

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class EngineService(
ILogger<EngineService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Engine>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Engine>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Engine> AddAsync(Engine entity)
{

View file

@ -1,21 +1,21 @@
using LANCommander.SDK;
using LANCommander.SDK.Interceptors;
using LANCommander.SDK.Models;
using LANCommander.Server.Services.Abstractions;
using LANCommander.Server.Services.Factories;
using LANCommander.Server.Services.Interceptors;
using LANCommander.Server.Services.MediaGrabbers;
using LANCommander.Server.Services.ServerEngines;
using Microsoft.Extensions.DependencyInjection;
using Settings = LANCommander.Server.Services.Models.Settings;
namespace LANCommander.Server.Services.Extensions;
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddLANCommanderServer(this IServiceCollection services, Settings settings)
public static IServiceCollection AddLANCommanderServer(this IServiceCollection services)
{
services.AddScoped<IGitHubService, GitHubService>();
services.AddScoped<IdentityContextFactory>();
services.AddScoped<SettingService>();
services.AddScoped<AuthenticationService>();
services.AddScoped<ArchiveClient>();
services.AddScoped<StorageLocationService>();
@ -63,6 +63,7 @@ public static class IServiceCollectionExtensions
services.AddSingleton<IServerEngine>(provider => provider.GetService<DockerServerEngine>());
services.AddSingleton<IPXRelayService>();
services.AddSingleton<IBeaconMessageInterceptor, BeaconMessageInterceptor>();
services.AddAutoMapper(cfg => { }, typeof(MappingProfile));
services.AddFusionCache();

View file

@ -2,8 +2,6 @@
using LANCommander.Server.Data;
using LANCommander.Server.Data.Models;
using LANCommander.Server.Services.Extensions;
using LANCommander.SDK;
using LANCommander.SDK.Enums;
using System.Linq.Expressions;
using ZiggyCreatures.Caching.Fusion;
using LANCommander.Server.Services.Models;
@ -15,10 +13,11 @@ namespace LANCommander.Server.Services
{
public class GameCustomFieldService(
ILogger<GameCustomFieldService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<GameCustomField>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<GameCustomField>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<GameCustomField> AddAsync(GameCustomField entity)
{

View file

@ -3,7 +3,6 @@ using LANCommander.Server.Data;
using LANCommander.Server.Data.Models;
using LANCommander.Helpers;
using LANCommander.SDK.Enums;
using LANCommander.Server.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -13,11 +12,12 @@ namespace LANCommander.Server.Services
{
public sealed class GameSaveService(
ILogger<GameSaveService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory,
StorageLocationService storageLocationService) : BaseDatabaseService<GameSave>(logger, cache, mapper, httpContextAccessor, contextFactory)
StorageLocationService storageLocationService) : BaseDatabaseService<GameSave>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<GameSave> AddAsync(GameSave entity)
{

View file

@ -6,7 +6,6 @@ using LANCommander.Server.Services.Extensions;
using LANCommander.SDK;
using LANCommander.SDK.Enums;
using System.Linq.Expressions;
using LANCommander.SDK.Services;
using ZiggyCreatures.Caching.Fusion;
using LANCommander.Server.Services.Models;
using Microsoft.Extensions.Logging;
@ -17,13 +16,14 @@ namespace LANCommander.Server.Services
{
public class GameService(
ILogger<GameService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory,
ArchiveClient archiveClient,
MediaService mediaService,
SDK.Services.ScriptClient scriptClient) : BaseDatabaseService<Game>(logger, cache, mapper, httpContextAccessor, contextFactory)
SDK.Services.ScriptClient scriptClient) : BaseDatabaseService<Game>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Game> AddAsync(Game entity)
{

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class GenreService(
ILogger<GenreService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Genre>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Genre>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Genre> AddAsync(Genre entity)
{

View file

@ -1,5 +1,6 @@
using LANCommander.Server.Services.Abstractions;
using LANCommander.Server.Services.Models;
using LANCommander.Server.Settings.Enums;
using Microsoft.Extensions.Logging;
using Octokit;
using Semver;

View file

@ -11,11 +11,12 @@ namespace LANCommander.Server.Services
{
public class IGDBService(
ILogger<IGDBService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
GameService gameService,
EngineService engineService,
CompanyService companyService,
GenreService genreService,
TagService tagService) : BaseService(logger), IDisposable
TagService tagService) : BaseService(logger, settingsProvider), IDisposable
{
private const string DefaultFields = "*";
private IGDBClient Client;
@ -32,8 +33,8 @@ namespace LANCommander.Server.Services
public void Authenticate()
{
ClientId = _settings.IGDBClientId;
ClientSecret = _settings.IGDBClientSecret;
ClientId = _settingsProvider.CurrentValue.Server.IGDB.ClientId;
ClientSecret = _settingsProvider.CurrentValue.Server.IGDB.ClientSecret;
try
{

View file

@ -9,23 +9,21 @@ namespace LANCommander.Server.Services
private Task? _relayTask;
private IPXRelay? _relay;
public IPXRelayService(ILogger<IPXRelayService> logger) : base(logger)
public IPXRelayService(ILogger<IPXRelayService> logger, SettingsProvider<Settings.Settings> settingsProvider) : base(logger, settingsProvider)
{
Init(logger);
}
public void Init(ILogger logger)
{
var settings = SettingService.GetSettings();
StopAsync().Wait();
_relay = new IPXRelay(settings.IPXRelay.Port, logger);
_relay = new IPXRelay(_settingsProvider.CurrentValue.Server.IPXRelay.Port, logger);
if (!settings.IPXRelay.Logging)
if (!_settingsProvider.CurrentValue.Server.IPXRelay.Logging)
_relay.DisableLogging();
if (settings.IPXRelay.Enabled)
if (_settingsProvider.CurrentValue.Server.IPXRelay.Enabled)
{
_relayCts = new CancellationTokenSource();
_relayTask = Task.Run(async () =>

View file

@ -1,22 +1,19 @@
using System.Net;
using LANCommander.SDK.Interceptors;
using LANCommander.SDK.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Interceptors;
namespace LANCommander.Server.Services.Interceptors;
public class BeaconMessageInterceptor : IBeaconMessageInterceptor
public class BeaconMessageInterceptor(SettingsProvider<Settings.Settings> settingsProvider) : IBeaconMessageInterceptor
{
public async Task<BeaconMessage> ExecuteAsync(BeaconMessage message, IPEndPoint interfaceEndPoint)
{
if (String.IsNullOrWhiteSpace(message.Address))
{
var settings = SettingService.GetSettings();
if (settings.UseSSL)
message.Address = $"https://{interfaceEndPoint.Address.ToString()}:{settings.SSLPort}";
if (settingsProvider.CurrentValue.Server.Http.UseSSL)
message.Address = $"https://{interfaceEndPoint.Address.ToString()}:{settingsProvider.CurrentValue.Server.Http.SSLPort}";
else
message.Address = $"http://{interfaceEndPoint.Address.ToString()}:{settings.Port}";
message.Address = $"http://{interfaceEndPoint.Address.ToString()}:{settingsProvider.CurrentValue.Server.Http.Port}";
}
return message;

View file

@ -1,4 +1,5 @@
using LANCommander.Server.Data;
using System.Configuration;
using LANCommander.Server.Data;
using LANCommander.Server.Data.Models;
using AutoMapper;
using Microsoft.AspNetCore.Http;
@ -10,10 +11,11 @@ namespace LANCommander.Server.Services
{
public sealed class IssueService(
ILogger<IssueService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Issue>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Issue>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Issue> AddAsync(Issue entity)
{

View file

@ -11,10 +11,11 @@ namespace LANCommander.Server.Services
{
public sealed class KeyService(
ILogger<KeyService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Key>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Key>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Key> AddAsync(Key entity)
{

View file

@ -29,6 +29,7 @@
<ItemGroup>
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj" />
<ProjectReference Include="..\LANCommander.Server.Data\LANCommander.Server.Data.csproj" />
<ProjectReference Include="..\LANCommander.Server.Settings\LANCommander.Server.Settings.csproj" />
<ProjectReference Include="..\LANCommander.Steam\LANCommander.Steam.csproj" />
</ItemGroup>

View file

@ -1,4 +1,5 @@
using LANCommander.Server.Data;
using System.Configuration;
using LANCommander.Server.Data;
using LANCommander.Server.Data.Models;
using Microsoft.Extensions.Logging;
using AutoMapper;
@ -11,12 +12,13 @@ namespace LANCommander.Server.Services
{
public sealed class LibraryService(
ILogger<LibraryService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory,
GameService gameService,
UserService userService) : BaseDatabaseService<Library>(logger, cache, mapper, httpContextAccessor, contextFactory)
UserService userService) : BaseDatabaseService<Library>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Library> AddAsync(Library entity)
{

View file

@ -53,7 +53,7 @@ namespace LANCommander.Server
dest.Cover,
opt => opt.MapFrom(src => src.Media.Where(m => m.Type == SDK.Enums.MediaType.Cover).FirstOrDefault()));
CreateMap<Services.Models.AuthenticationProvider, SDK.Models.AuthenticationProvider>();
CreateMap<Settings.Models.AuthenticationProvider, SDK.Models.AuthenticationProvider>();
CreateMap<Data.Models.Game, EntityReference>()
.ForMember(dest => dest.Name,

View file

@ -20,11 +20,9 @@ namespace LANCommander.Server.Services.MediaGrabbers
SteamGridDbFormats.Webp
};
public SteamGridDBMediaGrabber()
public SteamGridDBMediaGrabber(SettingsProvider<Settings.Settings> settingsProvider)
{
var settings = SettingService.GetSettings();
SteamGridDb = new SteamGridDb(settings.Media.SteamGridDbApiKey);
SteamGridDb = new SteamGridDb(settingsProvider.CurrentValue.Server.Media.SteamGridDbApiKey);
SteamClient = new SteamClient();
}

View file

@ -1,4 +1,5 @@
using LANCommander.Server.Data;
using System.Configuration;
using LANCommander.Server.Data;
using LANCommander.Server.Data.Models;
using LANCommander.Helpers;
using Syncfusion.PdfToImageConverter;
@ -20,11 +21,12 @@ namespace LANCommander.Server.Services
{
public sealed class MediaService(
ILogger<MediaService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory,
StorageLocationService storageLocationService) : BaseDatabaseService<Media>(logger, cache, mapper, httpContextAccessor, contextFactory)
StorageLocationService storageLocationService) : BaseDatabaseService<Media>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Media> AddAsync(Media entity)
{
@ -118,7 +120,7 @@ namespace LANCommander.Server.Services
public string GetThumbnailPath(Media media)
{
var config = _settings.Media.GetMediaTypeConfig(media.Type);
var config = _settingsProvider.CurrentValue.Server.Media.GetMediaTypeConfig(media.Type);
if (config != null && config.Thumbnails.Enabled)
return $"{GetMediaPath(media)}.Thumb";
@ -175,7 +177,7 @@ namespace LANCommander.Server.Services
if (!File.Exists(source))
return String.Empty;
var config = _settings.Media.GetMediaTypeConfig(media.Type);
var config = _settingsProvider.CurrentValue.Server.Media.GetMediaTypeConfig(media.Type);
Stream stream = null;

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class MultiplayerModeService(
ILogger<MultiplayerModeService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<MultiplayerMode>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<MultiplayerMode>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<MultiplayerMode> AddAsync(MultiplayerMode entity)
{

View file

@ -15,10 +15,11 @@ namespace LANCommander.Server.Services
{
public sealed class PageService(
ILogger<PageService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Page>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Page>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Page> AddAsync(Page entity)
{

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class PlatformService(
ILogger<PlatformService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Platform>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Platform>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Platform> AddAsync(Platform entity)
{

View file

@ -11,11 +11,12 @@ namespace LANCommander.Server.Services
{
public sealed class PlaySessionService(
ILogger<PlaySessionService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory,
ServerService serverService) : BaseDatabaseService<PlaySession>(logger, cache, mapper, httpContextAccessor, contextFactory)
ServerService serverService) : BaseDatabaseService<PlaySession>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<PlaySession> AddAsync(PlaySession entity)
{

View file

@ -11,10 +11,11 @@ namespace LANCommander.Server.Services
{
public sealed class RedistributableService(
ILogger<SDK.Services.RedistributableClient> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Redistributable>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Redistributable>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Redistributable> AddAsync(Redistributable entity)
{

View file

@ -14,13 +14,14 @@ namespace LANCommander.Server.Services
{
public sealed class RoleService(
ILogger<RoleService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory,
CollectionService collectionService,
IdentityContextFactory identityContextFactory,
RoleManager<Role> roleManager) : BaseDatabaseService<Role>(logger, cache, mapper, httpContextAccessor, contextFactory)
RoleManager<Role> roleManager) : BaseDatabaseService<Role>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public const string AdministratorRoleName = "Administrator";

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class SavePathService(
ILogger<SavePathService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<SavePath>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<SavePath>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<SavePath> AddAsync(SavePath entity)
{

View file

@ -13,10 +13,11 @@ namespace LANCommander.Server.Services
{
public sealed class ScriptService(
ILogger<ScriptService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Script>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Script>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Script> AddAsync(Script script)
{

View file

@ -9,6 +9,8 @@ using LANCommander.Server.Data.Enums;
using LANCommander.Server.Services.Abstractions;
using LANCommander.Server.Services.Enums;
using LANCommander.Server.Services.Models;
using LANCommander.Server.Settings.Enums;
using LANCommander.Server.Settings.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@ -18,6 +20,7 @@ namespace LANCommander.Server.Services.ServerEngines;
public class DockerServerEngine(
ILogger<DockerServerEngine> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IServiceProvider serviceProvider,
IMapper mapper,
IOptions<SDK.Models.Settings> settings) : IServerEngine
@ -30,9 +33,7 @@ public class DockerServerEngine(
public async Task InitializeAsync()
{
var settings = SettingService.GetSettings();
foreach (var serverEngineConfig in settings.Servers.ServerEngines.Where(se => se.Type == ServerEngine.Docker))
foreach (var serverEngineConfig in settingsProvider.CurrentValue.Server.GameServers.ServerEngines.Where(se => se.Type == ServerEngine.Docker))
{
if (serverEngineConfig != null && !String.IsNullOrWhiteSpace(serverEngineConfig.Address) && Uri.TryCreate(serverEngineConfig.Address, UriKind.Absolute, out var hostAddress))
_dockerClients[serverEngineConfig.Id] = new DockerClientConfiguration(hostAddress).CreateClient();
@ -118,7 +119,7 @@ public class DockerServerEngine(
logger?.LogInformation("Executing script \"{ScriptName}\"", serverScript.Name);
if (settings.Value.Debug.EnableScriptDebugging)
if (settingsProvider.CurrentValue.Debug.EnableScriptDebugging)
script.EnableDebug();
await script.ExecuteAsync<int>();
@ -171,7 +172,7 @@ public class DockerServerEngine(
logger?.LogInformation("Executing script \"{ScriptName}\"", serverScript.Name);
if (settings.Value.Debug.EnableScriptDebugging)
if (settingsProvider.CurrentValue.Debug.EnableScriptDebugging)
script.EnableDebug();
await script.ExecuteAsync<int>();

View file

@ -10,6 +10,7 @@ using LANCommander.Server.Services.Abstractions;
using LANCommander.Server.Services.Enums;
using LANCommander.Server.Services.Models;
using LANCommander.Server.Services.Utilities;
using LANCommander.Server.Settings.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

View file

@ -14,10 +14,11 @@ namespace LANCommander.Server.Services
{
public sealed class ServerHttpPathService(
ILogger<ServerHttpPathService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<ServerHttpPath>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<ServerHttpPath>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<ServerHttpPath> AddAsync(ServerHttpPath entity)
{

View file

@ -12,10 +12,11 @@ namespace LANCommander.Server.Services
{
public sealed class ServerConsoleService(
ILogger<ServerConsoleService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<ServerConsole>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<ServerConsole>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<ServerConsole> AddAsync(ServerConsole entity)
{

View file

@ -16,12 +16,13 @@ namespace LANCommander.Server.Services
{
public sealed class ServerService(
ILogger<ServerService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory,
IServiceProvider serviceProvider,
UserService userService) : BaseDatabaseService<Data.Models.Server>(logger, cache, mapper, httpContextAccessor, contextFactory)
UserService userService) : BaseDatabaseService<Data.Models.Server>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Data.Models.Server> AddAsync(Data.Models.Server entity)
{

View file

@ -1,88 +0,0 @@
using LANCommander.SDK.Factories;
using LANCommander.Server.Models;
using LANCommander.Server.Services.Factories;
using Microsoft.Extensions.DependencyInjection;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace LANCommander.Server.Services
{
public class SettingService
{
public static string WorkingDirectory { get; set; } = "";
private const string FileName = "Settings.Server.yml";
public static string SettingsFile
{
get
{
if (!string.IsNullOrWhiteSpace(WorkingDirectory))
return Path.Combine(WorkingDirectory, FileName);
else
return FileName;
}
}
private static Models.Settings Settings { get; set; }
public static Models.Settings LoadSettings()
{
if (File.Exists(SettingsFile))
{
var contents = File.ReadAllText(SettingsFile);
var deserializer = new DeserializerBuilder()
.IgnoreUnmatchedProperties()
.WithNamingConvention(PascalCaseNamingConvention.Instance)
.Build();
Settings = deserializer.Deserialize<Models.Settings>(contents);
}
else
{
Settings = new Models.Settings();
SaveSettings(Settings);
}
return Settings;
}
public static void WriteSettings(Models.Settings settings)
{
if (settings == null)
return;
var serializer = YamlSerializerFactory.Create();
File.WriteAllText(SettingsFile, serializer.Serialize(settings));
}
public static Models.Settings GetSettings(bool forceLoad = false)
{
if (Settings == null || forceLoad)
Settings = LoadSettings();
return Settings;
}
public static void SaveSettings(Models.Settings settings, IServiceProvider? serviceProvider = null)
{
WriteSettings(settings);
Settings = settings;
ReloadSettings(settings, serviceProvider);
}
private static void ReloadSettings(Models.Settings settings, IServiceProvider? serviceProvider)
{
if (serviceProvider == null || settings == null)
return;
if (serviceProvider.GetService<UserService>() is var service)
{
service!.Reconfigure(settings);
}
}
}
}

View file

@ -2,6 +2,7 @@
using LANCommander.Server.Data;
using LANCommander.Server.Data.Enums;
using LANCommander.Server.Data.Models;
using LANCommander.Server.Settings.Enums;
using Microsoft.AspNetCore.Identity;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
@ -14,8 +15,9 @@ namespace LANCommander.Server.Services
{
public class SetupService(
ILogger<SetupService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
UserManager<User> userManager,
IServiceProvider serviceProvider) : BaseService(logger), IDisposable
IServiceProvider serviceProvider) : BaseService(logger, settingsProvider), IDisposable
{
public async Task<bool> IsSetupInitialized()
{

View file

@ -6,20 +6,21 @@ using System.Text;
namespace LANCommander.Server.Services;
public class SteamCMDService(
ILogger<SteamCMDService> logger) : BaseService(logger)
ILogger<SteamCMDService> logger,
SettingsProvider<Settings.Settings> settingsProvider) : BaseService(logger, settingsProvider)
{
public async Task<SteamCmdConnectionStatus> GetConnectionStatusAsync(string username)
{
try
{
// Auto-populate SteamCMD path if not configured
if (String.IsNullOrWhiteSpace(_settings.SteamCMD.Path))
if (String.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
{
var detectedPath = await AutoDetectSteamCmdPathAsync();
if (!String.IsNullOrWhiteSpace(detectedPath))
{
_settings.SteamCMD.Path = detectedPath;
_settingsProvider.CurrentValue.Server.SteamCMD.Path = detectedPath;
_logger.LogInformation("Auto-detected SteamCMD at: {Path}", detectedPath);
}
else
@ -29,9 +30,9 @@ public class SteamCMDService(
}
}
if (!File.Exists(_settings.SteamCMD.Path))
if (!File.Exists(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
{
_logger.LogWarning("SteamCMD executable not found at configured path: {Path}", _settings.SteamCMD.Path);
_logger.LogWarning("SteamCMD executable not found at configured path: {Path}", _settingsProvider.CurrentValue.Server.SteamCMD.Path);
return SteamCmdConnectionStatus.NotInstalled;
}
@ -164,15 +165,15 @@ public class SteamCMDService(
{
try
{
if (string.IsNullOrWhiteSpace(_settings.SteamCMD.Path))
if (string.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
{
_logger.LogError("SteamCMD path is not configured");
return false;
}
if (!File.Exists(_settings.SteamCMD.Path))
if (!File.Exists(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
{
_logger.LogError("SteamCMD executable not found at configured path: {Path}", _settings.SteamCMD.Path);
_logger.LogError("SteamCMD executable not found at configured path: {Path}", _settingsProvider.CurrentValue.Server.SteamCMD.Path);
return false;
}
@ -204,15 +205,15 @@ public class SteamCMDService(
{
try
{
if (string.IsNullOrWhiteSpace(_settings.SteamCMD.Path))
if (string.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
{
_logger.LogError("SteamCMD path is not configured");
return false;
}
if (!File.Exists(_settings.SteamCMD.Path))
if (!File.Exists(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
{
_logger.LogError("SteamCMD executable not found at configured path: {Path}", _settings.SteamCMD.Path);
_logger.LogError("SteamCMD executable not found at configured path: {Path}", _settingsProvider.CurrentValue.Server.SteamCMD.Path);
return false;
}
@ -242,15 +243,15 @@ public class SteamCMDService(
{
try
{
if (string.IsNullOrWhiteSpace(_settings.SteamCMD.Path))
if (string.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
{
_logger.LogError("SteamCMD path is not configured");
return false;
}
if (!File.Exists(_settings.SteamCMD.Path))
if (!File.Exists(_settingsProvider.CurrentValue.Server.SteamCMD.Path))
{
_logger.LogError("SteamCMD executable not found at configured path: {Path}", _settings.SteamCMD.Path);
_logger.LogError("SteamCMD executable not found at configured path: {Path}", _settingsProvider.CurrentValue.Server.SteamCMD.Path);
return false;
}
@ -330,7 +331,7 @@ public class SteamCMDService(
{
try
{
var executablePath = String.IsNullOrWhiteSpace(steamCmdPath) ? _settings.SteamCMD.Path : steamCmdPath;
var executablePath = String.IsNullOrWhiteSpace(steamCmdPath) ? _settingsProvider.CurrentValue.Server.SteamCMD.Path : steamCmdPath;
var processStartInfo = new ProcessStartInfo
{

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class StorageLocationService(
ILogger<StorageLocationService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<StorageLocation>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<StorageLocation>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<StorageLocation> AddAsync(StorageLocation entity)
{

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class TagService(
ILogger<TagService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Tag>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Tag>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Tag> AddAsync(Tag entity)
{

View file

@ -12,6 +12,7 @@ using LANCommander.Server.Services.Abstractions;
using LANCommander.Server.Services.Enums;
using LANCommander.Server.Services.Exceptions;
using LANCommander.Server.Services.Models;
using LANCommander.Server.Settings.Enums;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
@ -19,11 +20,12 @@ namespace LANCommander.Server.Services
{
public sealed class UpdateService(
ILogger<UpdateService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IHostApplicationLifetime applicationLifetime,
IServiceProvider serviceProvider,
IVersionProvider versionProvider,
IGitHubService gitHubService,
ServerService serverService) : BaseService(logger)
ServerService serverService) : BaseService(logger, settingsProvider)
{
public const string ArtifactUrlBase = "/download/Launcher/";
@ -31,12 +33,12 @@ namespace LANCommander.Server.Services
{
List<LauncherArtifact> launchers = [];
if (_settings.Launcher.HostUpdates)
if (_settingsProvider.CurrentValue.Server.Launcher.HostUpdates)
{
launchers.AddRange(GetLauncherArtifactsFromLocalFiles());
}
if (launchers.Count == 0 || _settings.Launcher.IncludeOnlineUpdates)
if (launchers.Count == 0 || _settingsProvider.CurrentValue.Server.Launcher.IncludeOnlineUpdates)
{
var githubLaunchers = await GetLauncherArtifactsFromGitHubAsync().ToListAsync();
launchers.AddRange(githubLaunchers);
@ -48,8 +50,8 @@ namespace LANCommander.Server.Services
public IEnumerable<LauncherArtifact> GetLauncherArtifactsFromLocalFiles()
{
var currentVersion = versionProvider.GetCurrentVersion();
var downloadedLaunchers = Directory.GetFiles(_settings.Launcher.StoragePath, $"LANCommander.Launcher*v{currentVersion.WithoutMetadata()}.*");
var downloadedInstallers = Directory.GetFiles(_settings.Launcher.StoragePath, $"LANCommander.Launcher-{currentVersion.WithoutMetadata()}*Setup*.*");
var downloadedLaunchers = Directory.GetFiles(_settingsProvider.CurrentValue.Server.Launcher.StoragePath, $"LANCommander.Launcher*v{currentVersion.WithoutMetadata()}.*");
var downloadedInstallers = Directory.GetFiles(_settingsProvider.CurrentValue.Server.Launcher.StoragePath, $"LANCommander.Launcher-{currentVersion.WithoutMetadata()}*Setup*.*");
var downloads = downloadedLaunchers.Concat(downloadedInstallers).Distinct();
@ -61,21 +63,21 @@ namespace LANCommander.Server.Services
{
var currentVersion = versionProvider.GetCurrentVersion();
if (!String.IsNullOrWhiteSpace(_settings.Launcher.VersionOverride))
currentVersion = SemVersion.Parse(_settings.Launcher.VersionOverride, SemVersionStyles.AllowV);
if (!String.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.Launcher.VersionOverride))
currentVersion = SemVersion.Parse(_settingsProvider.CurrentValue.Server.Launcher.VersionOverride, SemVersionStyles.AllowV);
var releaseChannel = versionProvider.GetReleaseChannel(currentVersion);
if (releaseChannel != _settings.Update.ReleaseChannel)
releaseChannel = _settings.Update.ReleaseChannel;
if (releaseChannel != _settingsProvider.CurrentValue.Server.Update.ReleaseChannel)
releaseChannel = _settingsProvider.CurrentValue.Server.Update.ReleaseChannel;
var tag = $"v{currentVersion.WithoutMetadata()}";
if (releaseChannel == ReleaseChannel.Nightly)
tag = "nightly";
if (!String.IsNullOrWhiteSpace(_settings.Launcher.VersionOverride))
tag = _settings.Launcher.VersionOverride;
if (!String.IsNullOrWhiteSpace(_settingsProvider.CurrentValue.Server.Launcher.VersionOverride))
tag = _settingsProvider.CurrentValue.Server.Launcher.VersionOverride;
logger.LogInformation($"Searching for artifacts for v{currentVersion.WithoutMetadata()}");
@ -127,7 +129,7 @@ namespace LANCommander.Server.Services
public async Task<bool> UpdateAvailableAsync()
{
var latestVersion = await gitHubService.GetLatestVersionAsync(_settings.Update.ReleaseChannel);
var latestVersion = await gitHubService.GetLatestVersionAsync(_settingsProvider.CurrentValue.Server.Update.ReleaseChannel);
var sortOrder = versionProvider.GetCurrentVersion().ComparePrecedenceTo(latestVersion);
@ -164,7 +166,7 @@ namespace LANCommander.Server.Services
client.DownloadFileCompleted += ReleaseDownloaded;
client.QueryString.Add("Version", release.TagName);
await client.DownloadFileTaskAsync(new Uri(releaseFile), Path.Combine(_settings.Update.StoragePath, $"{release.TagName}.zip"));
await client.DownloadFileTaskAsync(new Uri(releaseFile), Path.Combine(_settingsProvider.CurrentValue.Server.Update.StoragePath, $"{release.TagName}.zip"));
}
}
@ -196,7 +198,7 @@ namespace LANCommander.Server.Services
var uri = new Uri(releaseFile);
client.QueryString.Add("Version", release.TagName);
await client.DownloadFileTaskAsync(uri, Path.Combine(_settings.Update.StoragePath, $"{release.TagName}.zip"));
await client.DownloadFileTaskAsync(uri, Path.Combine(_settingsProvider.CurrentValue.Server.Update.StoragePath, $"{release.TagName}.zip"));
}
}
}
@ -231,7 +233,7 @@ namespace LANCommander.Server.Services
private void ReleaseDownloaded(object? sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
string version = ((WebClient)sender).QueryString["Version"];
string path = Path.Combine(_settings.Update.StoragePath, $"{version}.zip");
string path = Path.Combine(_settingsProvider.CurrentValue.Server.Update.StoragePath, $"{version}.zip");
_logger?.LogInformation("Update version {Version} has been downloaded", version);
@ -257,7 +259,7 @@ namespace LANCommander.Server.Services
var process = new ProcessStartInfo();
process.FileName = processExecutable;
process.Arguments = $"-Version {version} -Path \"{_settings.Update.StoragePath}\" -Executable {Process.GetCurrentProcess().MainModule.FileName}";
process.Arguments = $"-Version {version} -Path \"{_settingsProvider.CurrentValue.Server.Update.StoragePath}\" -Executable {Process.GetCurrentProcess().MainModule.FileName}";
process.UseShellExecute = true;
Process.Start(process);
@ -274,12 +276,12 @@ namespace LANCommander.Server.Services
public string GetLauncherFileLocation(string objectKey)
{
return Path.Combine(_settings.Launcher.StoragePath, objectKey);
return Path.Combine(_settingsProvider.CurrentValue.Server.Launcher.StoragePath, objectKey);
}
public LauncherArtifact GetLauncherArtifact(string objectKey)
{
string name = Path.Combine(_settings.Launcher.StoragePath, objectKey);
string name = Path.Combine(_settingsProvider.CurrentValue.Server.Launcher.StoragePath, objectKey);
return GetArtifactFromName(name);
}
}

View file

@ -10,10 +10,11 @@ namespace LANCommander.Server.Services
{
public sealed class UserCustomFieldService(
ILogger<UserCustomFieldService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<UserCustomField>(logger, cache, mapper, httpContextAccessor, contextFactory)
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<UserCustomField>(logger, settingsProvider, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<UserCustomField> AddAsync(UserCustomField entity)
{

View file

@ -27,11 +27,12 @@ namespace LANCommander.Server.Services
public UserService(
ILogger<UserService> logger,
SettingsProvider<Settings.Settings> settingsProvider,
IMapper mapper,
IFusionCache cache,
CollectionService collectionService,
IDbContextFactory<DatabaseContext> contextFactory,
IdentityContextFactory identityContextFactory) : base(logger)
IdentityContextFactory identityContextFactory) : base(logger, settingsProvider)
{
IdentityContext = identityContextFactory.Create();
CollectionService = collectionService;
@ -40,17 +41,17 @@ namespace LANCommander.Server.Services
Cache = cache;
}
public void Reconfigure(Settings settings)
public void Reconfigure()
{
var options = IdentityContext.UserManager.Options;
if (options == null || settings == null)
if (options == null)
return;
options.Password.RequireNonAlphanumeric = settings.Authentication.PasswordRequireNonAlphanumeric;
options.Password.RequireLowercase = settings.Authentication.PasswordRequireLowercase;
options.Password.RequireUppercase = settings.Authentication.PasswordRequireUppercase;
options.Password.RequireDigit = settings.Authentication.PasswordRequireDigit;
options.Password.RequiredLength = settings.Authentication.PasswordRequiredLength;
options.Password.RequireNonAlphanumeric = _settingsProvider.CurrentValue.Server.Authentication.PasswordRequireNonAlphanumeric;
options.Password.RequireLowercase = _settingsProvider.CurrentValue.Server.Authentication.PasswordRequireLowercase;
options.Password.RequireUppercase = _settingsProvider.CurrentValue.Server.Authentication.PasswordRequireUppercase;
options.Password.RequireDigit = _settingsProvider.CurrentValue.Server.Authentication.PasswordRequireDigit;
options.Password.RequiredLength = _settingsProvider.CurrentValue.Server.Authentication.PasswordRequiredLength;
}
public async Task<User> GetAsync(string userName)

View file

@ -15,10 +15,11 @@ namespace LANCommander.Server.Services
{
public abstract class BaseDatabaseService<T>(
ILogger logger,
SettingsProvider<Settings.Settings> settingsProvider,
IFusionCache cache,
IMapper mapper,
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> dbContextFactory) : BaseService(logger), IBaseDatabaseService<T> where T : class, IBaseModel
IDbContextFactory<DatabaseContext> dbContextFactory) : BaseService(logger, settingsProvider), IBaseDatabaseService<T> where T : class, IBaseModel
{
protected readonly List<Func<IQueryable<T>, IQueryable<T>>> _modifiers = new();

View file

@ -1,16 +1,16 @@
using LANCommander.Server.Services.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
namespace LANCommander.Server.Services
{
public abstract class BaseService
{
protected readonly ILogger _logger;
protected readonly Settings _settings = SettingService.GetSettings();
protected readonly SettingsProvider<Settings.Settings> _settingsProvider;
public BaseService(ILogger logger)
public BaseService(ILogger logger, SettingsProvider<Settings.Settings> settingsProvider)
{
_logger = logger;
_settingsProvider = settingsProvider;
Initialize();
}

View file

@ -1,5 +1,4 @@
using LANCommander.Server.Data.Enums;
using System.Text.RegularExpressions;
using Humanizer.Bytes;
using LANCommander.SDK.Enums;
using Microsoft.AspNetCore.Http;

View file

@ -0,0 +1,8 @@
namespace LANCommander.Server.Settings.Enums;
public enum AuthenticationProviderType
{
OAuth2,
OpenIdConnect,
Saml,
}

View file

@ -0,0 +1,9 @@
namespace LANCommander.Server.Settings.Enums;
public enum DatabaseProvider
{
Unknown,
SQLite,
MySQL,
PostgreSQL
}

View file

@ -0,0 +1,7 @@
namespace LANCommander.Server.Settings.Enums;
public enum LauncherArchitecture
{
x64,
arm64,
}

View file

@ -0,0 +1,8 @@
namespace LANCommander.Server.Settings.Enums;
public enum LauncherPlatform
{
Windows,
Linux,
macOS,
}

View file

@ -0,0 +1,11 @@
namespace LANCommander.Server.Settings.Enums;
public enum LogInterval
{
Infinite,
Year,
Month,
Day,
Hour,
Minute,
}

View file

@ -0,0 +1,10 @@
namespace LANCommander.Server.Settings.Enums;
public enum LoggingProviderType
{
File,
Console,
SignalR,
Seq,
ElasticSearch,
}

View file

@ -0,0 +1,8 @@
namespace LANCommander.Server.Settings.Enums;
public enum ReleaseChannel
{
Stable,
Prerelease,
Nightly,
}

View file

@ -0,0 +1,7 @@
namespace LANCommander.Server.Settings.Enums;
public enum ServerEngine
{
Local,
Docker,
}

View file

@ -0,0 +1,7 @@
namespace LANCommander.Server.Settings.Enums;
public enum Theme
{
Light,
Dark,
}

View file

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LANCommander.SDK\LANCommander.SDK.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,8 @@
namespace LANCommander.Server.Settings.Models;
public class ArchiveSettings
{
public bool EnablePatching { get; set; } = false;
public bool AllowInsecureDownloads { get; set; } = false;
public int MaxChunkSize { get; set; } = 50;
}

View file

@ -0,0 +1,26 @@
using LANCommander.Server.Settings.Enums;
namespace LANCommander.Server.Settings.Models;
public class AuthenticationProvider
{
public string Name { get; set; }
public string Slug { get; set; }
public AuthenticationProviderType Type { get; set; } = AuthenticationProviderType.OAuth2;
public string Color { get; set; }
public string Icon { get; set; }
public string Documentation { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string AuthorizationEndpoint { get; set; }
public string TokenEndpoint { get; set; }
public string UserInfoEndpoint { get; set; }
public string ConfigurationUrl { get; set; }
public IEnumerable<string> Scopes { get; set; } = new List<string>();
public IEnumerable<ClaimMapping> ClaimMappings { get; set; } = new List<ClaimMapping>();
public string GetCustomFieldName()
{
return $"ExternalId/{Slug}";
}
}

View file

@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Http;
namespace LANCommander.Server.Settings.Models;
public class AuthenticationSettings
{
public bool RequireApproval { get; set; } = false;
public string TokenSecret { get; set; } = Guid.NewGuid().ToString();
public int TokenLifetime { get; set; } = 30;
public bool PasswordRequireNonAlphanumeric { get; set; } = false;
public bool PasswordRequireLowercase { get; set; } = false;
public bool PasswordRequireUppercase { get; set; } = false;
public bool PasswordRequireDigit { get; set; } = true;
public int PasswordRequiredLength { get; set; } = 8;
public CookiePolicy HttpCookiePolicy { get; set; } = new()
{
SameSite = SameSiteMode.Lax,
Secure = CookieSecurePolicy.None
};
public CookiePolicy HttpsCookiePolicy { get; set; } = new()
{
SameSite = SameSiteMode.None,
Secure = CookieSecurePolicy.SameAsRequest
};
public IEnumerable<AuthenticationProvider> AuthenticationProviders { get; set; } = new List<AuthenticationProvider>();
}

View file

@ -0,0 +1,6 @@
namespace LANCommander.Server.Settings.Models;
public class BackupSettings
{
public string StoragePath { get; set; } = "Backups";
}

View file

@ -0,0 +1,9 @@
namespace LANCommander.Server.Settings.Models;
public class BeaconSettings
{
public bool Enabled { get; set; } = true;
public string Name { get; set; } = "LANCommander";
public string Address { get; set; } = String.Empty;
public int Port { get; set; } = 35891;
}

View file

@ -0,0 +1,7 @@
namespace LANCommander.Server.Settings.Models;
public class ClaimMapping
{
public string Name { get; set; }
public string Value { get; set; }
}

View file

@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Http;
namespace LANCommander.Server.Settings.Models;
public class CookiePolicy
{
public SameSiteMode SameSite { get; set; } = SameSiteMode.None;
public CookieSecurePolicy Secure { get; set; } = CookieSecurePolicy.SameAsRequest;
}

View file

@ -0,0 +1,9 @@
using LANCommander.Server.Settings.Enums;
namespace LANCommander.Server.Settings.Models;
public class DatabaseSettings
{
public DatabaseProvider Provider { get; set; } = DatabaseProvider.Unknown;
public string ConnectionString { get; set; } = String.Empty;
}

View file

@ -0,0 +1,22 @@
using LANCommander.Server.Settings.Enums;
namespace LANCommander.Server.Settings.Models;
public class GameServerSettings
{
public string StoragePath { get; set; } = "Servers";
public IEnumerable<ServerEngineConfiguration> ServerEngines { get; set; } = new List<ServerEngineConfiguration>()
{
new ServerEngineConfiguration
{
Name = "Local",
Type = ServerEngine.Local,
},
new ServerEngineConfiguration
{
Name = "Docker",
Type = ServerEngine.Docker,
Address = "unix:///var/run/docker.sock",
}
};
}

View file

@ -0,0 +1,10 @@
namespace LANCommander.Server.Settings.Models;
public class HttpSettings
{
public int Port { get; set; } = 1337;
public int SSLPort { get; set; } = 31337;
public bool UseSSL { get; set; } = false;
public string CertificatePath { get; set; } = String.Empty;
public string CertificatePassword { get; set; } = String.Empty;
}

View file

@ -0,0 +1,7 @@
namespace LANCommander.Server.Settings.Models;
public class IGDBSettings
{
public string ClientId { get; set; } = String.Empty;
public string ClientSecret { get; set; } = String.Empty;
}

View file

@ -0,0 +1,9 @@
namespace LANCommander.Server.Settings.Models;
public class IPXRelaySettings
{
public bool Enabled { get; set; } = false;
public string Host { get; set; } = String.Empty;
public int Port { get; set; } = 213;
public bool Logging { get; set; } = false;
}

View file

@ -0,0 +1,19 @@
using LANCommander.Server.Settings.Enums;
namespace LANCommander.Server.Settings.Models;
public class LauncherSettings
{
public string StoragePath { get; set; } = "Launcher";
/// <summary>
/// Whether to include locally downloaded launcher files and provide these for download
/// </summary>
public bool HostUpdates { get; set; } = true;
/// <summary>
/// Whether to include online launcher files to link to for download
/// </summary>
public bool IncludeOnlineUpdates { get; set; } = false;
public string VersionOverride { get; set; } = String.Empty;
public IEnumerable<LauncherArchitecture> Architectures { get; set; } = new[] { LauncherArchitecture.x64, LauncherArchitecture.arm64 };
public IEnumerable<LauncherPlatform> Platforms { get; set; } = new[] { LauncherPlatform.Windows };
}

View file

@ -0,0 +1,6 @@
namespace LANCommander.Server.Settings.Models;
public class LibrarySettings
{
public bool EnableUserLibraries { get; set; } = true;
}

View file

@ -0,0 +1,29 @@
using LANCommander.Server.Settings.Enums;
using Microsoft.Extensions.Logging;
namespace LANCommander.Server.Settings.Models;
public class LogSettings
{
public bool IgnorePings { get; set; } = true;
public IEnumerable<LoggingProvider> Providers { get; set; } = [
new()
{
Name = "Console",
MinimumLevel = LogLevel.Information, Type = LoggingProviderType.Console
},
new()
{
Name = "File",
MinimumLevel = LogLevel.Information,
Type = LoggingProviderType.File,
ConnectionString = "Logs"
},
new()
{
Name = "Server Console",
MinimumLevel = LogLevel.Information,
Type = LoggingProviderType.SignalR
},
];
}

View file

@ -0,0 +1,15 @@
using LANCommander.Server.Settings.Enums;
using Microsoft.Extensions.Logging;
namespace LANCommander.Server.Settings.Models;
public class LoggingProvider
{
public string Name { get; set; }
public LogLevel MinimumLevel { get; set; } = LogLevel.Information;
public bool Enabled { get; set; } = true;
public LoggingProviderType Type { get; set; } = LoggingProviderType.Console;
public LogInterval? ArchiveEvery { get; set; }
public int MaxArchiveFiles { get; set; } = 10;
public string ConnectionString { get; set; } = String.Empty;
}

View file

@ -0,0 +1,86 @@
using Humanizer.Bytes;
using LANCommander.SDK.Enums;
namespace LANCommander.Server.Settings.Models;
public class MediaSettings
{
public string SteamGridDbApiKey { get; set; } = String.Empty;
public IEnumerable<MediaTypeSettings> MediaTypes { get; set; } =
new List<MediaTypeSettings>()
{
new MediaTypeSettings
{
Type = MediaType.Icon,
MaxFileSize = 2 * ByteSize.BytesInMegabyte,
Thumbnails = new MediaTypeThumbnailSettings
{
MinSize = new ThumbnailSize(32, 32),
MaxSize = new ThumbnailSize(128, 128),
}
},
new MediaTypeSettings
{
Type = MediaType.Cover,
MaxFileSize = 6 * ByteSize.BytesInMegabyte,
Thumbnails = new MediaTypeThumbnailSettings
{
MinSize = new ThumbnailSize(240, 360),
MaxSize = new ThumbnailSize(600, 900),
}
},
new MediaTypeSettings
{
Type = MediaType.Background,
MaxFileSize = 8 * ByteSize.BytesInMegabyte,
Thumbnails = new MediaTypeThumbnailSettings
{
MinSize = new ThumbnailSize(690, 310),
MaxSize = new ThumbnailSize(3840, 1240),
}
},
new MediaTypeSettings
{
Type = MediaType.Avatar,
MaxFileSize = 2 * ByteSize.BytesInMegabyte,
Thumbnails = new MediaTypeThumbnailSettings
{
MinSize = new ThumbnailSize(64, 64),
MaxSize = new ThumbnailSize(256, 256),
}
},
new MediaTypeSettings
{
Type = MediaType.Logo,
MaxFileSize = 4 * ByteSize.BytesInMegabyte,
Thumbnails = new MediaTypeThumbnailSettings
{
MinSize = new ThumbnailSize(400, 400),
MaxSize = new ThumbnailSize(1000, 1000),
}
},
new MediaTypeSettings
{
Type = MediaType.Manual,
MaxFileSize = 4 * ByteSize.BytesInMegabyte,
Thumbnails = new MediaTypeThumbnailSettings
{
MinSize = new ThumbnailSize(240, 360),
MaxSize = new ThumbnailSize(600, 900),
}
},
new MediaTypeSettings
{
Type = MediaType.PageImage,
MaxFileSize = 4 * ByteSize.BytesInMegabyte,
Thumbnails = new MediaTypeThumbnailSettings
{
MinSize = new ThumbnailSize(480, 480),
MaxSize = new ThumbnailSize(1920, 1920),
}
},
};
public MediaTypeSettings? GetMediaTypeConfig(MediaType type) => MediaTypes.FirstOrDefault(x => x.Type == type);
}

View file

@ -0,0 +1,10 @@
using LANCommander.SDK.Enums;
namespace LANCommander.Server.Settings.Models;
public class MediaTypeSettings
{
public MediaType Type { get; set; }
public long MaxFileSize { get; set; }
public MediaTypeThumbnailSettings Thumbnails { get; set; } = new();
}

View file

@ -0,0 +1,10 @@
namespace LANCommander.Server.Settings.Models;
public class MediaTypeThumbnailSettings
{
public ThumbnailSize MinSize { get; set; }
public ThumbnailSize MaxSize { get; set; }
public int Scale { get; set; } = 50;
public bool Enabled { get; set; } = true;
public int Quality { get; set; } = 75;
}

View file

@ -0,0 +1,6 @@
namespace LANCommander.Server.Settings.Models;
public class MetadataSettings
{
public IGDBSettings IGDB { get; set; }
}

View file

@ -0,0 +1,7 @@
namespace LANCommander.Server.Settings.Models;
public class RoleSettings
{
public Guid DefaultRoleId { get; set; }
public bool RestrictGamesByCollection { get; set; } = false;
}

View file

@ -0,0 +1,7 @@
namespace LANCommander.Server.Settings.Models;
public class ScriptSettings
{
public bool EnableAutomaticRepackaging { get; set; } = false;
public int RepackageEvery { get; set; } = 24;
}

View file

@ -0,0 +1,11 @@
using LANCommander.Server.Settings.Enums;
namespace LANCommander.Server.Settings.Models;
public class ServerEngineConfiguration
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = "Local";
public ServerEngine Type { get; set; } = ServerEngine.Local;
public string Address { get; set; } = String.Empty;
}

View file

@ -0,0 +1,23 @@
namespace LANCommander.Server.Settings.Models;
public class ServerSettings
{
public ArchiveSettings Archives { get; set; } = new();
public AuthenticationSettings Authentication { get; set; } = new();
public BackupSettings Backups { get; set; } = new();
public BeaconSettings Beacon { get; set; } = new();
public DatabaseSettings Database { get; set; } = new();
public HttpSettings Http { get; set; } = new();
public IGDBSettings IGDB { get; set; } = new();
public IPXRelaySettings IPXRelay { get; set; } = new();
public LauncherSettings Launcher { get; set; } = new();
public LibrarySettings Library { get; set; } = new();
public LogSettings Logs { get; set; } = new();
public MediaSettings Media { get; set; } = new();
public RoleSettings Roles { get; set; } = new();
public ScriptSettings Scripts { get; set; } = new();
public GameServerSettings GameServers { get; set; } = new();
public SteamCmdSettings SteamCMD { get; set; } = new();
public UpdateSettings Update { get; set; } = new();
public UserSaveSettings UserSaves { get; set; } = new();
}

View file

@ -0,0 +1,7 @@
namespace LANCommander.Server.Settings.Models;
public class SteamCmdProfile
{
public string Username { get; set; } = String.Empty;
public string InstallDirectory { get; set; } = String.Empty;
}

View file

@ -0,0 +1,8 @@
namespace LANCommander.Server.Settings.Models;
public class SteamCmdSettings
{
public string Path { get; set; } = String.Empty;
public string InstallDirectory { get; set; } = "";
public ICollection<SteamCmdProfile> Profiles { get; set; } = new List<SteamCmdProfile>();
}

View file

@ -0,0 +1,17 @@
namespace LANCommander.Server.Settings.Models;
public class ThumbnailSize
{
public int Width { get; set; }
public int Height { get; set; }
public ThumbnailSize()
{
}
public ThumbnailSize(int width, int height)
{
Width = width;
Height = height;
}
}

View file

@ -0,0 +1,9 @@
using LANCommander.Server.Settings.Enums;
namespace LANCommander.Server.Settings.Models;
public class UpdateSettings
{
public string StoragePath { get; set; } = "Updates";
public ReleaseChannel ReleaseChannel { get; set; } = ReleaseChannel.Stable;
}

Some files were not shown because too many files have changed in this diff Show more