From 2cbbc00489692744871a2fa62ca7ee39c0a5d795 Mon Sep 17 00:00:00 2001 From: Fishandchips321 Date: Thu, 5 Mar 2026 19:09:26 +0000 Subject: [PATCH] fix(admin): fixed various issues lmao idk it was a ballache and is probably quite shit. sorry to future me --- backend/src/Controllers/AuthController.cs | 13 +-- backend/src/Controllers/SearchController.cs | 4 +- backend/src/Controllers/ServersController.cs | 53 +++++++---- .../src/Exceptions/AuthServiceExceptions.cs | 1 - .../Exceptions/ServerRepositoryExceptions.cs | 7 ++ ...20260305184556_changedServerId.Designer.cs | 87 +++++++++++++++++++ .../20260305184556_changedServerId.cs | 48 ++++++++++ .../DatabaseContextModelSnapshot.cs | 11 +-- .../ControllerBodies/Auth/CreateLoginDTO.cs | 7 ++ .../ControllerBodies/Auth/DeleteLoginDTO.cs | 6 ++ .../ControllerBodies/Auth/UpdateLoginDTO.cs | 7 ++ .../ControllerBodies/Servers/AddServerDTO.cs | 8 ++ .../Servers/DeleteServerDTO.cs | 6 ++ .../Servers/UpdateServerDTO.cs | 8 ++ backend/src/Models/Server.cs | 5 +- backend/src/Models/ServerDTO.cs | 2 - backend/src/Repositories/IServerRepository.cs | 6 +- backend/src/Repositories/ServerRepository.cs | 17 ++-- backend/src/Services/AuthService.cs | 13 ++- backend/src/Services/ClientService.cs | 68 +++++++++------ backend/src/Services/IAuthService.cs | 2 +- backend/src/Services/IClientService.cs | 6 +- backend/src/Services/ILibraryService.cs | 4 +- backend/src/Services/ISearchService.cs | 2 +- backend/src/Services/IServerService.cs | 6 +- backend/src/Services/LibraryService.cs | 16 ++-- backend/src/Services/SearchService.cs | 40 ++++----- backend/src/Services/ServerService.cs | 56 ++++++++++-- .../Components/ServerSearch/ServerSearch.tsx | 4 +- 29 files changed, 382 insertions(+), 131 deletions(-) create mode 100644 backend/src/Migrations/20260305184556_changedServerId.Designer.cs create mode 100644 backend/src/Migrations/20260305184556_changedServerId.cs create mode 100644 backend/src/Models/ControllerBodies/Auth/CreateLoginDTO.cs create mode 100644 backend/src/Models/ControllerBodies/Auth/DeleteLoginDTO.cs create mode 100644 backend/src/Models/ControllerBodies/Auth/UpdateLoginDTO.cs create mode 100644 backend/src/Models/ControllerBodies/Servers/AddServerDTO.cs create mode 100644 backend/src/Models/ControllerBodies/Servers/DeleteServerDTO.cs create mode 100644 backend/src/Models/ControllerBodies/Servers/UpdateServerDTO.cs diff --git a/backend/src/Controllers/AuthController.cs b/backend/src/Controllers/AuthController.cs index caa9c97..ce96511 100644 --- a/backend/src/Controllers/AuthController.cs +++ b/backend/src/Controllers/AuthController.cs @@ -1,4 +1,5 @@ using JellyGlass.Exceptions; +using JellyGlass.Models.ControllerBodies.Auth; using JellyGlass.Services; using Microsoft.AspNetCore.Mvc; @@ -64,20 +65,20 @@ public class AuthController : ControllerBase } [HttpPost] - public async Task CreateLogin([FromForm] string username, [FromForm] string password) + public async Task CreateLogin([FromBody] CreateLoginDTO login) { if (!await IsAdminAuthenticated()) { return Forbid(); } - var newLogin = await _service.CreateLogin(username, password); + var newLogin = await _service.CreateLogin(login.Username, login.Password); return Ok(newLogin); } [HttpPut] - public async Task UpdateLogin([FromForm] string username, [FromForm] string password, [FromForm] string newPassword) + public async Task UpdateLogin([FromBody] UpdateLoginDTO login) { if (!await IsAdminAuthenticated()) { @@ -86,7 +87,7 @@ public class AuthController : ControllerBase try { - await _service.UpdateLoginOwnPassword(username, newPassword, password); + await _service.UpdateLoginOwnPassword(GetSessionToken()!, login.NewPassword, login.Password); //TODO: should take in session token } catch (AuthNotFoundException) { @@ -101,7 +102,7 @@ public class AuthController : ControllerBase } [HttpDelete] - public async Task DeleteLogin([FromForm] string username) + public async Task DeleteLogin([FromBody] DeleteLoginDTO login) { if (!await IsAdminAuthenticated()) { @@ -110,7 +111,7 @@ public class AuthController : ControllerBase try { - var deletedLogin = await _service.DeleteLogin(username); + var deletedLogin = await _service.DeleteLogin(login.Username); return Ok(deletedLogin); } diff --git a/backend/src/Controllers/SearchController.cs b/backend/src/Controllers/SearchController.cs index b635e49..9a6f9d3 100644 --- a/backend/src/Controllers/SearchController.cs +++ b/backend/src/Controllers/SearchController.cs @@ -21,7 +21,7 @@ public class SearchController : ControllerBase } [HttpGet] - public async Task handleSearch([FromQuery] string searchTerm, string serverId) + public async Task handleSearch([FromQuery] string searchTerm, string serverUrl) { var sessionToken = Request.Cookies["session"]; @@ -34,7 +34,7 @@ public class SearchController : ControllerBase try { - var results = await _service.Search(decodedSearchTerm, serverId); + var results = await _service.Search(decodedSearchTerm, serverUrl); return Ok(results); } catch (ClientNotFoundException) diff --git a/backend/src/Controllers/ServersController.cs b/backend/src/Controllers/ServersController.cs index 4c75bbe..9daf602 100644 --- a/backend/src/Controllers/ServersController.cs +++ b/backend/src/Controllers/ServersController.cs @@ -1,4 +1,6 @@ +using System.Web; using JellyGlass.Exceptions; +using JellyGlass.Models.ControllerBodies.Servers; using JellyGlass.Services; using Microsoft.AspNetCore.Mvc; @@ -33,11 +35,17 @@ public class ServersController : ControllerBase } [HttpGet] - public async Task GetServer([FromQuery] int id) + public async Task GetServer([FromQuery] string url) { + if (!await IsAuthenticated()) + { + return Unauthorized(); + } + try { - var server = await _serverService.GetServerByID(id); + var decodedUrl = HttpUtility.UrlDecode(url); + var server = await _serverService.GetServerByUrl(decodedUrl); return Ok(server); } catch (ServerNotFoundException) @@ -47,7 +55,7 @@ public class ServersController : ControllerBase } [HttpPut] - public async Task UpdateServer([FromBody] int id, string owner, string url, string apiToken) + public async Task UpdateServer([FromBody] UpdateServerDTO server) { if (!await IsAdminAuthenticated()) { @@ -56,7 +64,7 @@ public class ServersController : ControllerBase try { - var updatedServer = await _serverService.UpdateServer(id, owner, url, apiToken); + var updatedServer = await _serverService.UpdateServer(server.Owner, server.Url, server.ApiToken); return Ok(updatedServer); } catch (ServerNotFoundException) @@ -66,20 +74,7 @@ public class ServersController : ControllerBase } [HttpPost] - public async Task AddServer([FromBody] string owner, string url, string apiToken) - { - if (!await IsAdminAuthenticated()) - { - return Forbid(); - } - - var newServer = await _serverService.CreateServer(owner, url, apiToken); - - return Ok(newServer); - } - - [HttpDelete] - public async Task DeleteServer([FromBody] int id) + public async Task AddServer([FromBody] AddServerDTO server) { if (!await IsAdminAuthenticated()) { @@ -88,7 +83,27 @@ public class ServersController : ControllerBase try { - var deletedServer = await _serverService.DeleteServer(id); + var newServer = await _serverService.CreateServer(server.Owner, server.Url, server.ApiToken); + return Ok(newServer); + } + catch (ServerAlreadyExistsException) + { + return Conflict("A server with this url already exists"); + } + } + + [HttpDelete] + public async Task DeleteServer([FromBody] DeleteServerDTO server) + { + if (!await IsAdminAuthenticated()) + { + return Forbid(); + } + + try + { + var decodedUrl = HttpUtility.UrlDecode(server.Url); + var deletedServer = await _serverService.DeleteServer(decodedUrl); return Ok(deletedServer); } catch (ServerNotFoundException) diff --git a/backend/src/Exceptions/AuthServiceExceptions.cs b/backend/src/Exceptions/AuthServiceExceptions.cs index 29c2a58..057becc 100644 --- a/backend/src/Exceptions/AuthServiceExceptions.cs +++ b/backend/src/Exceptions/AuthServiceExceptions.cs @@ -7,7 +7,6 @@ public class AuthServiceException : Exception public AuthServiceException(string message, Exception inner) : base(message, inner) { } } -[System.Serializable] public class LoginFailedException : AuthServiceException { public LoginFailedException() { } diff --git a/backend/src/Exceptions/ServerRepositoryExceptions.cs b/backend/src/Exceptions/ServerRepositoryExceptions.cs index bd3c6ca..4253b83 100644 --- a/backend/src/Exceptions/ServerRepositoryExceptions.cs +++ b/backend/src/Exceptions/ServerRepositoryExceptions.cs @@ -12,4 +12,11 @@ public class ServerNotFoundException : ServerRepositoryException public ServerNotFoundException() { } public ServerNotFoundException(string message) : base(message) { } public ServerNotFoundException(string message, Exception inner) : base(message, inner) { } +} + +public class ServerAlreadyExistsException : ServerRepositoryException +{ + public ServerAlreadyExistsException() { } + public ServerAlreadyExistsException(string message) : base(message) { } + public ServerAlreadyExistsException(string message, System.Exception inner) : base(message, inner) { } } \ No newline at end of file diff --git a/backend/src/Migrations/20260305184556_changedServerId.Designer.cs b/backend/src/Migrations/20260305184556_changedServerId.Designer.cs new file mode 100644 index 0000000..898e67d --- /dev/null +++ b/backend/src/Migrations/20260305184556_changedServerId.Designer.cs @@ -0,0 +1,87 @@ +// +using System; +using JellyGlass.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace JellyGlassBackend.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20260305184556_changedServerId")] + partial class changedServerId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.11"); + + modelBuilder.Entity("JellyGlass.Models.Server", b => + { + b.Property("Url") + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Url"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("JellyGlass.Models.UserLogin", b => + { + b.Property("Username") + .HasColumnType("TEXT"); + + b.Property("HashedPassword") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.HasKey("Username"); + + b.ToTable("Logins"); + }); + + modelBuilder.Entity("JellyGlass.Models.UserSession", b => + { + b.Property("SessionToken") + .HasColumnType("TEXT"); + + b.Property("ExpiresOn") + .HasColumnType("TEXT"); + + b.Property("LoginUsername") + .HasColumnType("TEXT"); + + b.HasKey("SessionToken"); + + b.HasIndex("LoginUsername"); + + b.ToTable("Sessions"); + }); + + modelBuilder.Entity("JellyGlass.Models.UserSession", b => + { + b.HasOne("JellyGlass.Models.UserLogin", "Login") + .WithMany() + .HasForeignKey("LoginUsername"); + + b.Navigation("Login"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Migrations/20260305184556_changedServerId.cs b/backend/src/Migrations/20260305184556_changedServerId.cs new file mode 100644 index 0000000..5570749 --- /dev/null +++ b/backend/src/Migrations/20260305184556_changedServerId.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace JellyGlassBackend.Migrations +{ + /// + public partial class changedServerId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_Servers", + table: "Servers"); + + migrationBuilder.DropColumn( + name: "Id", + table: "Servers"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Servers", + table: "Servers", + column: "Url"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_Servers", + table: "Servers"); + + migrationBuilder.AddColumn( + name: "Id", + table: "Servers", + type: "INTEGER", + nullable: false, + defaultValue: 0) + .Annotation("Sqlite:Autoincrement", true); + + migrationBuilder.AddPrimaryKey( + name: "PK_Servers", + table: "Servers", + column: "Id"); + } + } +} diff --git a/backend/src/Migrations/DatabaseContextModelSnapshot.cs b/backend/src/Migrations/DatabaseContextModelSnapshot.cs index 4c4f81e..7cf2a83 100644 --- a/backend/src/Migrations/DatabaseContextModelSnapshot.cs +++ b/backend/src/Migrations/DatabaseContextModelSnapshot.cs @@ -19,9 +19,8 @@ namespace JellyGlassBackend.Migrations modelBuilder.Entity("JellyGlass.Models.Server", b => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.Property("Url") + .HasColumnType("TEXT"); b.Property("ApiToken") .IsRequired() @@ -31,11 +30,7 @@ namespace JellyGlassBackend.Migrations .IsRequired() .HasColumnType("TEXT"); - b.Property("Url") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); + b.HasKey("Url"); b.ToTable("Servers"); }); diff --git a/backend/src/Models/ControllerBodies/Auth/CreateLoginDTO.cs b/backend/src/Models/ControllerBodies/Auth/CreateLoginDTO.cs new file mode 100644 index 0000000..e861020 --- /dev/null +++ b/backend/src/Models/ControllerBodies/Auth/CreateLoginDTO.cs @@ -0,0 +1,7 @@ +namespace JellyGlass.Models.ControllerBodies.Auth; + +public class CreateLoginDTO +{ + public required string Username { get; set; } + public required string Password { get; set; } +} \ No newline at end of file diff --git a/backend/src/Models/ControllerBodies/Auth/DeleteLoginDTO.cs b/backend/src/Models/ControllerBodies/Auth/DeleteLoginDTO.cs new file mode 100644 index 0000000..84b0163 --- /dev/null +++ b/backend/src/Models/ControllerBodies/Auth/DeleteLoginDTO.cs @@ -0,0 +1,6 @@ +namespace JellyGlass.Models.ControllerBodies.Auth; + +public class DeleteLoginDTO +{ + public required string Username { get; set; } +} \ No newline at end of file diff --git a/backend/src/Models/ControllerBodies/Auth/UpdateLoginDTO.cs b/backend/src/Models/ControllerBodies/Auth/UpdateLoginDTO.cs new file mode 100644 index 0000000..065b853 --- /dev/null +++ b/backend/src/Models/ControllerBodies/Auth/UpdateLoginDTO.cs @@ -0,0 +1,7 @@ +namespace JellyGlass.Models.ControllerBodies.Auth; + +public class UpdateLoginDTO +{ + public required string Password { get; set; } + public required string NewPassword { get; set; } +} \ No newline at end of file diff --git a/backend/src/Models/ControllerBodies/Servers/AddServerDTO.cs b/backend/src/Models/ControllerBodies/Servers/AddServerDTO.cs new file mode 100644 index 0000000..aa49703 --- /dev/null +++ b/backend/src/Models/ControllerBodies/Servers/AddServerDTO.cs @@ -0,0 +1,8 @@ +namespace JellyGlass.Models.ControllerBodies.Servers; + +public class AddServerDTO +{ + public required string Owner { get; set; } + public required string Url { get; set; } + public required string ApiToken { get; set; } +} \ No newline at end of file diff --git a/backend/src/Models/ControllerBodies/Servers/DeleteServerDTO.cs b/backend/src/Models/ControllerBodies/Servers/DeleteServerDTO.cs new file mode 100644 index 0000000..a9a808b --- /dev/null +++ b/backend/src/Models/ControllerBodies/Servers/DeleteServerDTO.cs @@ -0,0 +1,6 @@ +namespace JellyGlass.Models.ControllerBodies.Servers; + +public class DeleteServerDTO +{ + public required string Url { get; set; } +} \ No newline at end of file diff --git a/backend/src/Models/ControllerBodies/Servers/UpdateServerDTO.cs b/backend/src/Models/ControllerBodies/Servers/UpdateServerDTO.cs new file mode 100644 index 0000000..24eb654 --- /dev/null +++ b/backend/src/Models/ControllerBodies/Servers/UpdateServerDTO.cs @@ -0,0 +1,8 @@ +namespace JellyGlass.Models.ControllerBodies.Servers; + +public class UpdateServerDTO +{ + public required string Owner { get; set; } + public required string Url { get; set; } + public required string ApiToken { get; set; } +} \ No newline at end of file diff --git a/backend/src/Models/Server.cs b/backend/src/Models/Server.cs index 2a22275..d4b3e7c 100644 --- a/backend/src/Models/Server.cs +++ b/backend/src/Models/Server.cs @@ -1,14 +1,11 @@ using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; namespace JellyGlass.Models; public class Server { public string Owner { get; set; } = string.Empty; - public string Url { get; set; } = string.Empty; [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } + public string Url { get; set; } = string.Empty; public string ApiToken { get; set; } = string.Empty; } \ No newline at end of file diff --git a/backend/src/Models/ServerDTO.cs b/backend/src/Models/ServerDTO.cs index 585da9e..57e00e1 100644 --- a/backend/src/Models/ServerDTO.cs +++ b/backend/src/Models/ServerDTO.cs @@ -8,12 +8,10 @@ public class ServerDTO { Owner = s.Owner; Url = s.Url; - Id = s.Id; } public string Owner { get; set; } = string.Empty; public string Url { get; set; } = string.Empty; - public int Id { get; set; } public string JellyfinServerID { get; set; } = string.Empty; public bool Errored { get; set; } = false; } \ No newline at end of file diff --git a/backend/src/Repositories/IServerRepository.cs b/backend/src/Repositories/IServerRepository.cs index d5f142b..1094e51 100644 --- a/backend/src/Repositories/IServerRepository.cs +++ b/backend/src/Repositories/IServerRepository.cs @@ -5,8 +5,8 @@ namespace JellyGlass.Repositories; public interface IServerRepository { public Task GetServers(); - public Task GetServerById(int id); + public Task GetServerByUrl(string url); public Task CreateServer(string owner, string url, string apiToken); - public Task UpdateServer(int id, string owner, string url, string apiToken); - public Task DeleteServer(int id); + public Task UpdateServer(string owner, string url, string apiToken); + public Task DeleteServer(string url); } \ No newline at end of file diff --git a/backend/src/Repositories/ServerRepository.cs b/backend/src/Repositories/ServerRepository.cs index c6ceda9..f5ea4e5 100644 --- a/backend/src/Repositories/ServerRepository.cs +++ b/backend/src/Repositories/ServerRepository.cs @@ -20,9 +20,9 @@ public class ServerRepository : IServerRepository return servers; } - public async Task GetServerById(int id) + public async Task GetServerByUrl(string url) { - var server = await _context.Servers.FirstOrDefaultAsync(s => s.Id == id); + var server = await _context.Servers.FirstOrDefaultAsync(s => s.Url == url); if (server == null) { @@ -34,6 +34,10 @@ public class ServerRepository : IServerRepository public async Task CreateServer(string owner, string url, string apiToken) { + if (_context.Servers.Where(s => s.Url == url).Any()) + { + throw new ServerAlreadyExistsException(); + } var newServer = new Server() { @@ -48,12 +52,11 @@ public class ServerRepository : IServerRepository return newServer; } - public async Task UpdateServer(int id, string owner, string url, string apiToken) + public async Task UpdateServer(string owner, string url, string apiToken) { - var server = await GetServerById(id); + var server = await GetServerByUrl(url); server.Owner = owner; - server.Url = url; server.ApiToken = apiToken; _context.Servers.Update(server); @@ -62,9 +65,9 @@ public class ServerRepository : IServerRepository return server; } - public async Task DeleteServer(int id) + public async Task DeleteServer(string url) { - var server = await GetServerById(id); + var server = await GetServerByUrl(url); _context.Servers.Remove(server); await _context.SaveChangesAsync(); diff --git a/backend/src/Services/AuthService.cs b/backend/src/Services/AuthService.cs index 290008b..eaff320 100644 --- a/backend/src/Services/AuthService.cs +++ b/backend/src/Services/AuthService.cs @@ -107,16 +107,23 @@ public class AuthService : IAuthService return new UserLoginDTO(newLogin); } - public async Task UpdateLoginOwnPassword(string username, string newPassword, string oldPassword) + public async Task UpdateLoginOwnPassword(string sessionToken, string newPassword, string oldPassword) { - var login = await _loginRepo.GetUserLogin(username); + var session = await _sessionRepo.GetUserSession(sessionToken); + + if (session == null) + { + throw new LoginFailedException(); + } + + var login = session.Login; if (!BCrypt.Net.BCrypt.Verify(oldPassword, login.HashedPassword)) { throw new LoginFailedException(); } - await UpdateLoginPassword(username, newPassword); + await UpdateLoginPassword(login.Username, newPassword); } public async Task UpdateLoginPassword(string username, string newPassword) diff --git a/backend/src/Services/ClientService.cs b/backend/src/Services/ClientService.cs index 9383517..23d1cc4 100644 --- a/backend/src/Services/ClientService.cs +++ b/backend/src/Services/ClientService.cs @@ -1,4 +1,5 @@ using JellyGlass.Exceptions; +using JellyGlass.Models; using JellyGlass.Repositories; namespace JellyGlass.Services; @@ -6,7 +7,7 @@ namespace JellyGlass.Services; public class ClientService : IClientService { private IServerRepository _repository; - private static JellyfinApiClient[] _clients = []; + private static List _clients = new List(); private ILogger _logger; public ClientService(IServerRepository repository, ILogger logger) @@ -22,59 +23,70 @@ public class ClientService : IClientService await LoadClients(); } - return _clients; + return _clients.ToArray(); } - public async Task GetClientForServerId(string serverId) + public async Task GetClientFromUrl(string url) { if (!_clients.Any()) { await LoadClients(); } - foreach (var client in _clients) + var client = _clients.FirstOrDefault(c => c.InstanceUrl == url); + + if (client == null) { - if (client.ID == serverId) - { - return client; - } + throw new Exception($"Could not find client with url {url}"); } - throw new Exception($"Client with ID {serverId} not found"); + return client; + } + + public async Task LoadNewClient(Server server) + { + var client = new JellyfinApiClient(server.Url, server.ApiToken); + + try + { + await client.GetServerInfo(); + } + catch (JellyfinApiClientException e) + { + _logger.LogError($"Error authenticating to {server.Url}. Error: {e.Message} Client will not be used"); + throw; + } + + _clients.Add(client); + } + + public void UnloadClient(JellyfinApiClient client) + { + if (_clients.Contains(client)) + { + _clients.Remove(client); + } + else + { + throw new Exception($"Haven't loaded Jellyfin api client with ID of {client.ID}"); + } } private async Task LoadClients() { var servers = await _repository.GetServers(); - var clients = new List(); foreach (var server in servers) { - var client = new JellyfinApiClient(server.Url, server.ApiToken); - - // try - // { - // await client.Authenticate(); - // } - // catch (JellyfinApiClientException e) - // { - // _logger.LogError($"Error authenticating to {server.Url}. Error: {e.Message} Client will not be used"); - // continue; - // } - try { - await client.GetServerInfo(); //test the connection + await LoadNewClient(server); } - catch (JellyfinApiClientException e) + catch (JellyfinApiClientException) { - _logger.LogError($"Error authenticating to {server.Url}. Error: {e.Message} Client will not be used"); continue; } - - clients.Add(client); } - _clients = clients.ToArray(); } } \ No newline at end of file diff --git a/backend/src/Services/IAuthService.cs b/backend/src/Services/IAuthService.cs index 2c0549e..a83e31f 100644 --- a/backend/src/Services/IAuthService.cs +++ b/backend/src/Services/IAuthService.cs @@ -10,7 +10,7 @@ public interface IAuthService public Task GetLogins(); public Task GetLogin(string username); public Task CreateLogin(string username, string password); - public Task UpdateLoginOwnPassword(string username, string newPassword, string oldPassword); + public Task UpdateLoginOwnPassword(string sessionToken, string newPassword, string oldPassword); public Task UpdateLoginPassword(string username, string newPassword); public Task DeleteLogin(string username); } \ No newline at end of file diff --git a/backend/src/Services/IClientService.cs b/backend/src/Services/IClientService.cs index 4603415..f4d3191 100644 --- a/backend/src/Services/IClientService.cs +++ b/backend/src/Services/IClientService.cs @@ -1,3 +1,4 @@ +using JellyGlass.Models; using JellyGlass.Repositories; namespace JellyGlass.Services; @@ -5,6 +6,7 @@ namespace JellyGlass.Services; public interface IClientService { public Task GetClients(); - // public JellyfinApiClient GetClientForServer(string url); - public Task GetClientForServerId(string serverId); + public Task GetClientFromUrl(string url); + public Task LoadNewClient(Server server); + public void UnloadClient(JellyfinApiClient client); } \ No newline at end of file diff --git a/backend/src/Services/ILibraryService.cs b/backend/src/Services/ILibraryService.cs index 7d0be36..aa17053 100644 --- a/backend/src/Services/ILibraryService.cs +++ b/backend/src/Services/ILibraryService.cs @@ -5,9 +5,9 @@ namespace JellyGlass.Services; public interface ILibraryService { public Task GetLibraries(); - public Task GetItemsFromLibrary(string libraryName, string serverId); + public Task GetItemsFromLibrary(string libraryName, string serverUrl); - public Task GetLibrariesFromServer(string serverId); + public Task GetLibrariesFromServer(string serverUrl); // public Task GetChildrenFromItems(ItemDTO[] items); diff --git a/backend/src/Services/ISearchService.cs b/backend/src/Services/ISearchService.cs index d11638f..13ef6d1 100644 --- a/backend/src/Services/ISearchService.cs +++ b/backend/src/Services/ISearchService.cs @@ -4,5 +4,5 @@ namespace JellyGlass.Services; public interface ISearchService { - public Task Search(string searchTerm, string serverId); + public Task Search(string searchTerm, string serverUrl); } \ No newline at end of file diff --git a/backend/src/Services/IServerService.cs b/backend/src/Services/IServerService.cs index 0729725..91b9391 100644 --- a/backend/src/Services/IServerService.cs +++ b/backend/src/Services/IServerService.cs @@ -7,8 +7,8 @@ namespace JellyGlass.Services; public interface IServerService { public Task GetServers(); - public Task GetServerByID(int id); + public Task GetServerByUrl(string url); public Task CreateServer(string owner, string url, string apiToken); - public Task UpdateServer(int id, string owner, string url, string apiToken); - public Task DeleteServer(int id); + public Task UpdateServer(string owner, string url, string apiToken); + public Task DeleteServer(string url); } \ No newline at end of file diff --git a/backend/src/Services/LibraryService.cs b/backend/src/Services/LibraryService.cs index 9d5e96a..986273d 100644 --- a/backend/src/Services/LibraryService.cs +++ b/backend/src/Services/LibraryService.cs @@ -44,13 +44,13 @@ public class LibraryService : ILibraryService return libraries.Values.ToArray(); } - public async Task GetLibrary(string libraryName, string serverId) + public async Task GetLibrary(string libraryName, string serverUrl) { - var client = await _clientService.GetClientForServerId(serverId); + var client = await _clientService.GetClientFromUrl(serverUrl); ; if (client == null) { - throw new Exception($"Could not find client with ID of {serverId}"); + throw new Exception($"Could not find client with url of {serverUrl}"); } var libraries = await client.GetInstanceLibraries(); @@ -66,11 +66,11 @@ public class LibraryService : ILibraryService throw new Exception("Couldn't find library"); } - public async Task GetItemsFromLibrary(string libraryName, string serverId) + public async Task GetItemsFromLibrary(string libraryName, string serverUrl) { - var client = await _clientService.GetClientForServerId(serverId); + var client = await _clientService.GetClientFromUrl(serverUrl); - var library = await GetLibrary(libraryName, serverId); + var library = await GetLibrary(libraryName, serverUrl); var items = await client.GetItemChildren(library.Id); @@ -84,9 +84,9 @@ public class LibraryService : ILibraryService return dtos.ToArray(); } - public async Task GetLibrariesFromServer(string serverId) + public async Task GetLibrariesFromServer(string serverUrl) { - var client = await _clientService.GetClientForServerId(serverId); + var client = await _clientService.GetClientFromUrl(serverUrl); var libraries = new Dictionary(); diff --git a/backend/src/Services/SearchService.cs b/backend/src/Services/SearchService.cs index 77ed46e..2632e56 100644 --- a/backend/src/Services/SearchService.cs +++ b/backend/src/Services/SearchService.cs @@ -14,9 +14,9 @@ public class SearchService : ISearchService _clientService = clientService; } - public async Task Search(string searchTerm, string serverId) + public async Task Search(string searchTerm, string serverUrl) { - var client = await _clientService.GetClientForServerId(serverId); + var client = await _clientService.GetClientFromUrl(serverUrl); var items = await client.GetItems(searchTerm: searchTerm); @@ -30,25 +30,25 @@ public class SearchService : ISearchService return dtos.ToArray(); } - public async Task Search2(string searchTerm, string serverId) + // public async Task Search2(string searchTerm, int serverId) + // { + // var libraries = await _libraryService.GetLibrariesFromServer(serverId); + + // var foundItems = new List(); + + // foreach (var library in libraries) + // { + // var found = await SearchLibraryForTerm(searchTerm, serverId, library); + + // foundItems.AddRange(found); + // } + + // return foundItems.ToArray(); + // } + + private async Task SearchLibraryForTerm(string searchTerm, string serverUrl, Library library) { - var libraries = await _libraryService.GetLibrariesFromServer(serverId); - - var foundItems = new List(); - - foreach (var library in libraries) - { - var found = await SearchLibraryForTerm(searchTerm, serverId, library); - - foundItems.AddRange(found); - } - - return foundItems.ToArray(); - } - - private async Task SearchLibraryForTerm(string searchTerm, string serverId, Library library) - { - var items = await _libraryService.GetItemsFromLibrary(library.Name, serverId); + var items = await _libraryService.GetItemsFromLibrary(library.Name, serverUrl); var foundItems = new List(); diff --git a/backend/src/Services/ServerService.cs b/backend/src/Services/ServerService.cs index 1d1dad2..2f166d6 100644 --- a/backend/src/Services/ServerService.cs +++ b/backend/src/Services/ServerService.cs @@ -1,5 +1,6 @@ +using JellyGlass.Exceptions; using JellyGlass.Models; using JellyGlass.Repositories; @@ -49,31 +50,68 @@ public class ServerService : IServerService return dtos.ToArray(); } - public async Task GetServerByID(int id) + public async Task GetServerByUrl(string url) { - var server = await _repository.GetServerById(id); + var server = await _repository.GetServerByUrl(url); - return new ServerDTO(server); + return await GetServerDTO(server); } public async Task CreateServer(string owner, string url, string apiToken) { var newServer = await _repository.CreateServer(owner, url, apiToken); + var dto = new ServerDTO(newServer); - return new ServerDTO(newServer); + try + { + await _service.LoadNewClient(newServer); + } + catch (JellyfinApiClientException) + { + dto.Errored = true; + } + + return dto; } - public async Task UpdateServer(int id, string owner, string url, string apiToken) + public async Task UpdateServer(string owner, string url, string apiToken) { - var updatedServer = await _repository.UpdateServer(id, owner, url, apiToken); + var updatedServer = await _repository.UpdateServer(owner, url, apiToken); - return new ServerDTO(updatedServer); + return await GetServerDTO(updatedServer); } - public async Task DeleteServer(int id) + public async Task DeleteServer(string url) { - var deletedServer = await _repository.DeleteServer(id); + var deletedServer = await _repository.DeleteServer(url); + + try + { + var client = await _service.GetClientFromUrl(deletedServer.Url); + _service.UnloadClient(client); + } + catch (Exception) + { + //if it's not loaded, we don't care as we want to unload it anyway + } return new ServerDTO(deletedServer); } + + private async Task GetServerDTO(Server server) + { + var dto = new ServerDTO(server); + + try + { + var client = await _service.GetClientFromUrl(server.Url); + dto.JellyfinServerID = client.ID; + } + catch (Exception) + { + dto.Errored = true; + } + + return dto; + } } \ No newline at end of file diff --git a/frontend/src/Components/ServerSearch/ServerSearch.tsx b/frontend/src/Components/ServerSearch/ServerSearch.tsx index 843ab91..b93efd8 100644 --- a/frontend/src/Components/ServerSearch/ServerSearch.tsx +++ b/frontend/src/Components/ServerSearch/ServerSearch.tsx @@ -33,9 +33,9 @@ const ServerSearch = ({ searchTerm, server }: ServerSearchProps) => { searchResults.length > 0 ? searchResults.map(result => { return ( - + - + )