diff --git a/backend/src/Controllers/SearchController.cs b/backend/src/Controllers/SearchController.cs index 3fb5313..3e4ede0 100644 --- a/backend/src/Controllers/SearchController.cs +++ b/backend/src/Controllers/SearchController.cs @@ -1,3 +1,4 @@ +using JellyGlass.Services; using Microsoft.AspNetCore.Mvc; namespace JellyGlass.Controllers; @@ -7,15 +8,19 @@ namespace JellyGlass.Controllers; public class SearchController : ControllerBase { private ILogger _logger; + private ISearchService _service; - public SearchController(ILogger logger) + public SearchController(ILogger logger, ISearchService service) { _logger = logger; + _service = service; } [HttpGet] - public async Task handleSearch([FromQuery] string searchTerm) + public async Task handleSearch([FromQuery] string searchTerm, string serverId) { - throw new NotImplementedException(); + var results = await _service.Search(searchTerm, serverId); + + return Ok(results); } } \ No newline at end of file diff --git a/backend/src/Models/ItemDTO.cs b/backend/src/Models/ItemDTO.cs index 6cde21f..b9e26d6 100644 --- a/backend/src/Models/ItemDTO.cs +++ b/backend/src/Models/ItemDTO.cs @@ -16,6 +16,7 @@ public class ItemDTO Index = item.IndexNumber; ParentId = item.ParentId; ThumbnailUrl = $"{this.ServerUrl}/Items/{ID}/Images/Primary"; + ProductionYear = item.ProductionYear.ToString(); } public string ID { get; set; } = string.Empty; @@ -26,4 +27,5 @@ public class ItemDTO public int? Index { get; set; } public string? ParentId { get; set; } public string? ThumbnailUrl { get; set; } + public string? ProductionYear { get; set; } } \ No newline at end of file diff --git a/backend/src/Program.cs b/backend/src/Program.cs index b378c71..bc43a4e 100644 --- a/backend/src/Program.cs +++ b/backend/src/Program.cs @@ -27,6 +27,7 @@ builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddTransient(); +builder.Services.AddTransient(); var app = builder.Build(); diff --git a/backend/src/Repositories/IServerRepository.cs b/backend/src/Repositories/IServerRepository.cs index 489985f..4f07c87 100644 --- a/backend/src/Repositories/IServerRepository.cs +++ b/backend/src/Repositories/IServerRepository.cs @@ -5,4 +5,5 @@ namespace JellyGlass.Repositories; public interface IServerRepository { public Task GetServers(); + public Task GetServerById(string id); } \ No newline at end of file diff --git a/backend/src/Repositories/JellyfinApiClient.cs b/backend/src/Repositories/JellyfinApiClient.cs index 42928f7..05abf4c 100644 --- a/backend/src/Repositories/JellyfinApiClient.cs +++ b/backend/src/Repositories/JellyfinApiClient.cs @@ -15,6 +15,8 @@ public class JellyfinApiClient private readonly HttpClient _client; private readonly string _username, _password; + public string ID { get; private set; } = string.Empty; + public JellyfinApiClient(string instanceUrl, string username, string password) { InstanceUrl = instanceUrl; @@ -24,7 +26,7 @@ public class JellyfinApiClient _password = password; } - public async Task GetInstanceLibraries() + public async Task GetInstanceLibraries() { try { @@ -35,7 +37,7 @@ public class JellyfinApiClient var apiResponse = await response.Content.ReadFromJsonAsync(); - return apiResponse!; + return apiResponse.Items.ToArray(); } catch (HttpRequestException e) { @@ -43,7 +45,7 @@ public class JellyfinApiClient } } - public async Task GetItemChildren(string itemId) + public async Task GetItemChildren(string itemId) { try { @@ -55,7 +57,7 @@ public class JellyfinApiClient var apiResponse = await response.Content.ReadFromJsonAsync(); - return apiResponse!; + return apiResponse!.Items.ToArray(); } catch (HttpRequestException e) { @@ -63,16 +65,24 @@ public class JellyfinApiClient } } - public async Task GetItems(string searchTerm = "", string years = "", string itemTypes = "", string limit = "", string parentId = "") + public async Task GetItems(string searchTerm = "", string years = "", string itemTypes = "", string limit = "", string parentId = "") { - var query = new Dictionary(); - - if (searchTerm != String.Empty) + try { - query.Add("SearchTerm", searchTerm); - } + var request = new HttpRequestMessage(HttpMethod.Get, $"{InstanceUrl}/items?searchTerm={searchTerm}&recursive=true&includeItemTypes=Series,Movie"); - throw new NotImplementedException(); + var response = await MakeRequest(request); + + response.EnsureSuccessStatusCode(); + + var apiResponse = await response.Content.ReadFromJsonAsync(); + + return apiResponse!.Items.ToArray(); + } + catch (HttpRequestException e) + { + throw new JellyfinApiClientException(e.Message); + } } public async Task Authenticate() @@ -98,6 +108,7 @@ public class JellyfinApiClient var authResponse = await response.Content.ReadFromJsonAsync(); _apiKey = authResponse!.AccessToken; + ID = authResponse.ServerId; } catch (HttpRequestException e) { diff --git a/backend/src/Repositories/ServerRepository.cs b/backend/src/Repositories/ServerRepository.cs index 4fdb60d..e2a9558 100644 --- a/backend/src/Repositories/ServerRepository.cs +++ b/backend/src/Repositories/ServerRepository.cs @@ -18,4 +18,11 @@ public class ServerRepository : IServerRepository return servers; } + + public async Task GetServerById(string id) + { + var server = await _context.Servers.FirstOrDefaultAsync(s => s.Id == id); + + return server; + } } \ No newline at end of file diff --git a/backend/src/Services/ClientService.cs b/backend/src/Services/ClientService.cs index 92caead..f4c6a46 100644 --- a/backend/src/Services/ClientService.cs +++ b/backend/src/Services/ClientService.cs @@ -25,6 +25,24 @@ public class ClientService : IClientService return _clients; } + public async Task GetClientForServerId(string serverId) + { + if (!_clients.Any()) + { + await LoadClients(); + } + + foreach (var client in _clients) + { + if (client.ID == serverId) + { + return client; + } + } + + throw new Exception($"Client with ID {serverId} not found"); + } + private async Task LoadClients() { var servers = await _repository.GetServers(); diff --git a/backend/src/Services/IClientService.cs b/backend/src/Services/IClientService.cs index 137e365..4b32bdf 100644 --- a/backend/src/Services/IClientService.cs +++ b/backend/src/Services/IClientService.cs @@ -6,5 +6,5 @@ public interface IClientService { public Task GetJellyfinClients(); // public JellyfinApiClient GetClientForServer(string url); - // public JellyfinApiClient GetClientForServerId(string serverId); + public Task GetClientForServerId(string serverId); } \ No newline at end of file diff --git a/backend/src/Services/ILibraryService.cs b/backend/src/Services/ILibraryService.cs index 72a9e6f..7d0be36 100644 --- a/backend/src/Services/ILibraryService.cs +++ b/backend/src/Services/ILibraryService.cs @@ -5,7 +5,9 @@ namespace JellyGlass.Services; public interface ILibraryService { public Task GetLibraries(); - public Task GetItemsFromLibrary(string libraryName); + public Task GetItemsFromLibrary(string libraryName, string serverId); + + public Task GetLibrariesFromServer(string serverId); // public Task GetChildrenFromItems(ItemDTO[] items); diff --git a/backend/src/Services/ISearchService.cs b/backend/src/Services/ISearchService.cs new file mode 100644 index 0000000..d11638f --- /dev/null +++ b/backend/src/Services/ISearchService.cs @@ -0,0 +1,8 @@ +using JellyGlass.Models; + +namespace JellyGlass.Services; + +public interface ISearchService +{ + public Task Search(string searchTerm, string serverId); +} \ No newline at end of file diff --git a/backend/src/Services/LibraryService.cs b/backend/src/Services/LibraryService.cs index 882e4ed..2a0c28d 100644 --- a/backend/src/Services/LibraryService.cs +++ b/backend/src/Services/LibraryService.cs @@ -1,19 +1,20 @@ using JellyGlass.Models; +using JellyGlass.Models.JellyfinApi; namespace JellyGlass.Services; public class LibraryService : ILibraryService { - private IClientService _serverService; + private IClientService _clientService; public LibraryService(IClientService serverService) { - _serverService = serverService; + _clientService = serverService; } public async Task GetLibraries() { - var clients = await _serverService.GetJellyfinClients(); + var clients = await _clientService.GetJellyfinClients(); var libraries = new Dictionary(); @@ -21,7 +22,7 @@ public class LibraryService : ILibraryService { var clientLibraries = await client.GetInstanceLibraries(); - foreach (var library in clientLibraries.Items) + foreach (var library in clientLibraries) { if (library.Name == "Collections" || library.Name == "Playlists") { @@ -43,9 +44,73 @@ public class LibraryService : ILibraryService return libraries.Values.ToArray(); } - public async Task GetItemsFromLibrary(string libraryName) + public async Task GetLibrary(string libraryName, string serverId) { - throw new NotImplementedException(); + var client = await _clientService.GetClientForServerId(serverId); + + if (client == null) + { + throw new Exception($"Could not find client with ID of {serverId}"); + } + + var libraries = await client.GetInstanceLibraries(); + + foreach (var library in libraries) + { + if (library.Name == libraryName) + { + return library; + } + } + + throw new Exception("Couldn't find library"); + } + + public async Task GetItemsFromLibrary(string libraryName, string serverId) + { + var client = await _clientService.GetClientForServerId(serverId); + + var library = await GetLibrary(libraryName, serverId); + + var items = await client.GetItemChildren(library.Id); + + var dtos = new List(); + + foreach (var item in items) + { + dtos.Add(new ItemDTO(item, client.InstanceUrl)); + } + + return dtos.ToArray(); + } + + public async Task GetLibrariesFromServer(string serverId) + { + var client = await _clientService.GetClientForServerId(serverId); + + var libraries = new Dictionary(); + + var clientLibraries = await client.GetInstanceLibraries(); + + foreach (var library in clientLibraries) + { + if (library.Name == "Collections" || library.Name == "Playlists") + { + continue; + } + + if (!libraries.ContainsKey(library.Name)) + { + + libraries.Add(library.Name, new Library() + { + Name = library.Name, + ThumbnailUrl = $"{client.InstanceUrl}/Items/{library.Id}/Primary" + }); + } + } + + return libraries.Values.ToArray(); } // public async Task GetChildrenFromItems(ItemDTO[] items) diff --git a/backend/src/Services/SearchService.cs b/backend/src/Services/SearchService.cs new file mode 100644 index 0000000..77ed46e --- /dev/null +++ b/backend/src/Services/SearchService.cs @@ -0,0 +1,65 @@ +using JellyGlass.Models; +using JellyGlass.Models.JellyfinApi; + +namespace JellyGlass.Services; + +public class SearchService : ISearchService +{ + private ILibraryService _libraryService; + private IClientService _clientService; + + public SearchService(ILibraryService libraryService, IClientService clientService) + { + _libraryService = libraryService; + _clientService = clientService; + } + + public async Task Search(string searchTerm, string serverId) + { + var client = await _clientService.GetClientForServerId(serverId); + + var items = await client.GetItems(searchTerm: searchTerm); + + var dtos = new List(); + + foreach (var item in items) + { + dtos.Add(new ItemDTO(item, client.InstanceUrl)); + } + + return dtos.ToArray(); + } + + public async Task Search2(string searchTerm, string 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 serverId, Library library) + { + var items = await _libraryService.GetItemsFromLibrary(library.Name, serverId); + + var foundItems = new List(); + + foreach (var item in items) + { + if (item.Name.Contains(searchTerm, StringComparison.CurrentCultureIgnoreCase)) + { + foundItems.Add(item); + } + } + + return foundItems.ToArray(); + } +} \ No newline at end of file diff --git a/frontend/src/Components/ServerSearch/ServerSearch.tsx b/frontend/src/Components/ServerSearch/ServerSearch.tsx index 95f4dc8..e5c7951 100644 --- a/frontend/src/Components/ServerSearch/ServerSearch.tsx +++ b/frontend/src/Components/ServerSearch/ServerSearch.tsx @@ -15,6 +15,8 @@ const ServerSearch = ({ searchTerm, server }: ServerSearchProps) => { useEffect(() => { search(searchTerm, server.id).then(results => { setSearchResults(results); + }).catch(err => { + alert(err); }) }, [searchTerm]); @@ -22,7 +24,7 @@ const ServerSearch = ({ searchTerm, server }: ServerSearchProps) => { - + diff --git a/frontend/src/Components/ServerSearch/ServerSearchResult/ServerSearchResult.tsx b/frontend/src/Components/ServerSearch/ServerSearchResult/ServerSearchResult.tsx index b356262..755aaab 100644 --- a/frontend/src/Components/ServerSearch/ServerSearchResult/ServerSearchResult.tsx +++ b/frontend/src/Components/ServerSearch/ServerSearchResult/ServerSearchResult.tsx @@ -12,7 +12,7 @@ const ServerSearchResult = ({ searchResult, server }: ServerSearchResultProps) = return ( -

{searchResult.name}

+

{searchResult.name} - {searchResult.productionYear}

) } diff --git a/frontend/src/Lib/Search.ts b/frontend/src/Lib/Search.ts index def21e3..d0e3966 100644 --- a/frontend/src/Lib/Search.ts +++ b/frontend/src/Lib/Search.ts @@ -1,30 +1,21 @@ +import axios from "axios"; import type { Server } from "./Servers"; +import { apiUrl } from "./api"; export interface SearchResult { name: string; id: string; serverId: string; type: SearchResultType; - episodes: number; + productionYear: string; } export type SearchResultType = "movie" | "tv show" | "music"; export const search = async (searchTerm: string, serverId: string): Promise> => { - return [{ - name: "Test Result", - episodes: 0, - id: "awawa", - serverId: serverId, - type: "movie" - }, - { - name: "Test result 2", - episodes: 0, - id: "awawa 2", - serverId: serverId, - type: "movie" - }]; + const response = await axios.get>(`${apiUrl}/search?searchTerm=${searchTerm}&serverId=${serverId}`); + + return response.data; } export const getUrlForSearchResult = (result: SearchResult, server: Server): string => {
{server.name}{server.owner}'s server