Working search
This commit is contained in:
parent
271cf1f407
commit
2a572e8bc4
15 changed files with 217 additions and 39 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
using JellyGlass.Services;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace JellyGlass.Controllers;
|
namespace JellyGlass.Controllers;
|
||||||
|
|
@ -7,15 +8,19 @@ namespace JellyGlass.Controllers;
|
||||||
public class SearchController : ControllerBase
|
public class SearchController : ControllerBase
|
||||||
{
|
{
|
||||||
private ILogger<SearchController> _logger;
|
private ILogger<SearchController> _logger;
|
||||||
|
private ISearchService _service;
|
||||||
|
|
||||||
public SearchController(ILogger<SearchController> logger)
|
public SearchController(ILogger<SearchController> logger, ISearchService service)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_service = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> handleSearch([FromQuery] string searchTerm)
|
public async Task<IActionResult> handleSearch([FromQuery] string searchTerm, string serverId)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var results = await _service.Search(searchTerm, serverId);
|
||||||
|
|
||||||
|
return Ok(results);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ public class ItemDTO
|
||||||
Index = item.IndexNumber;
|
Index = item.IndexNumber;
|
||||||
ParentId = item.ParentId;
|
ParentId = item.ParentId;
|
||||||
ThumbnailUrl = $"{this.ServerUrl}/Items/{ID}/Images/Primary";
|
ThumbnailUrl = $"{this.ServerUrl}/Items/{ID}/Images/Primary";
|
||||||
|
ProductionYear = item.ProductionYear.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ID { get; set; } = string.Empty;
|
public string ID { get; set; } = string.Empty;
|
||||||
|
|
@ -26,4 +27,5 @@ public class ItemDTO
|
||||||
public int? Index { get; set; }
|
public int? Index { get; set; }
|
||||||
public string? ParentId { get; set; }
|
public string? ParentId { get; set; }
|
||||||
public string? ThumbnailUrl { get; set; }
|
public string? ThumbnailUrl { get; set; }
|
||||||
|
public string? ProductionYear { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ builder.Services.AddTransient<ILibraryService, LibraryService>();
|
||||||
builder.Services.AddTransient<IServerRepository, ServerRepository>();
|
builder.Services.AddTransient<IServerRepository, ServerRepository>();
|
||||||
builder.Services.AddScoped<IClientService, ClientService>();
|
builder.Services.AddScoped<IClientService, ClientService>();
|
||||||
builder.Services.AddTransient<IServerService, ServerService>();
|
builder.Services.AddTransient<IServerService, ServerService>();
|
||||||
|
builder.Services.AddTransient<ISearchService, SearchService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,5 @@ namespace JellyGlass.Repositories;
|
||||||
public interface IServerRepository
|
public interface IServerRepository
|
||||||
{
|
{
|
||||||
public Task<Server[]> GetServers();
|
public Task<Server[]> GetServers();
|
||||||
|
public Task<Server> GetServerById(string id);
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +15,8 @@ public class JellyfinApiClient
|
||||||
private readonly HttpClient _client;
|
private readonly HttpClient _client;
|
||||||
private readonly string _username, _password;
|
private readonly string _username, _password;
|
||||||
|
|
||||||
|
public string ID { get; private set; } = string.Empty;
|
||||||
|
|
||||||
public JellyfinApiClient(string instanceUrl, string username, string password)
|
public JellyfinApiClient(string instanceUrl, string username, string password)
|
||||||
{
|
{
|
||||||
InstanceUrl = instanceUrl;
|
InstanceUrl = instanceUrl;
|
||||||
|
|
@ -24,7 +26,7 @@ public class JellyfinApiClient
|
||||||
_password = password;
|
_password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ItemResponse> GetInstanceLibraries()
|
public async Task<Item[]> GetInstanceLibraries()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -35,7 +37,7 @@ public class JellyfinApiClient
|
||||||
|
|
||||||
var apiResponse = await response.Content.ReadFromJsonAsync<ItemResponse>();
|
var apiResponse = await response.Content.ReadFromJsonAsync<ItemResponse>();
|
||||||
|
|
||||||
return apiResponse!;
|
return apiResponse.Items.ToArray();
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
{
|
{
|
||||||
|
|
@ -43,7 +45,7 @@ public class JellyfinApiClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ItemResponse> GetItemChildren(string itemId)
|
public async Task<Item[]> GetItemChildren(string itemId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -55,7 +57,7 @@ public class JellyfinApiClient
|
||||||
|
|
||||||
var apiResponse = await response.Content.ReadFromJsonAsync<ItemResponse>();
|
var apiResponse = await response.Content.ReadFromJsonAsync<ItemResponse>();
|
||||||
|
|
||||||
return apiResponse!;
|
return apiResponse!.Items.ToArray();
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
{
|
{
|
||||||
|
|
@ -63,16 +65,24 @@ public class JellyfinApiClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ItemResponse> GetItems(string searchTerm = "", string years = "", string itemTypes = "", string limit = "", string parentId = "")
|
public async Task<Item[]> GetItems(string searchTerm = "", string years = "", string itemTypes = "", string limit = "", string parentId = "")
|
||||||
{
|
{
|
||||||
var query = new Dictionary<string, string>();
|
try
|
||||||
|
|
||||||
if (searchTerm != String.Empty)
|
|
||||||
{
|
{
|
||||||
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<ItemResponse>();
|
||||||
|
|
||||||
|
return apiResponse!.Items.ToArray();
|
||||||
|
}
|
||||||
|
catch (HttpRequestException e)
|
||||||
|
{
|
||||||
|
throw new JellyfinApiClientException(e.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Authenticate()
|
public async Task Authenticate()
|
||||||
|
|
@ -98,6 +108,7 @@ public class JellyfinApiClient
|
||||||
var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
||||||
|
|
||||||
_apiKey = authResponse!.AccessToken;
|
_apiKey = authResponse!.AccessToken;
|
||||||
|
ID = authResponse.ServerId;
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,11 @@ public class ServerRepository : IServerRepository
|
||||||
|
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Server> GetServerById(string id)
|
||||||
|
{
|
||||||
|
var server = await _context.Servers.FirstOrDefaultAsync(s => s.Id == id);
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +25,24 @@ public class ClientService : IClientService
|
||||||
return _clients;
|
return _clients;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<JellyfinApiClient> 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()
|
private async Task LoadClients()
|
||||||
{
|
{
|
||||||
var servers = await _repository.GetServers();
|
var servers = await _repository.GetServers();
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,5 @@ public interface IClientService
|
||||||
{
|
{
|
||||||
public Task<JellyfinApiClient[]> GetJellyfinClients();
|
public Task<JellyfinApiClient[]> GetJellyfinClients();
|
||||||
// public JellyfinApiClient GetClientForServer(string url);
|
// public JellyfinApiClient GetClientForServer(string url);
|
||||||
// public JellyfinApiClient GetClientForServerId(string serverId);
|
public Task<JellyfinApiClient> GetClientForServerId(string serverId);
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,9 @@ namespace JellyGlass.Services;
|
||||||
public interface ILibraryService
|
public interface ILibraryService
|
||||||
{
|
{
|
||||||
public Task<Library[]> GetLibraries();
|
public Task<Library[]> GetLibraries();
|
||||||
public Task<ItemDTO[]> GetItemsFromLibrary(string libraryName);
|
public Task<ItemDTO[]> GetItemsFromLibrary(string libraryName, string serverId);
|
||||||
|
|
||||||
|
public Task<Library[]> GetLibrariesFromServer(string serverId);
|
||||||
|
|
||||||
|
|
||||||
// public Task<ItemDTO[]> GetChildrenFromItems(ItemDTO[] items);
|
// public Task<ItemDTO[]> GetChildrenFromItems(ItemDTO[] items);
|
||||||
|
|
|
||||||
8
backend/src/Services/ISearchService.cs
Normal file
8
backend/src/Services/ISearchService.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
using JellyGlass.Models;
|
||||||
|
|
||||||
|
namespace JellyGlass.Services;
|
||||||
|
|
||||||
|
public interface ISearchService
|
||||||
|
{
|
||||||
|
public Task<ItemDTO[]> Search(string searchTerm, string serverId);
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
using JellyGlass.Models;
|
using JellyGlass.Models;
|
||||||
|
using JellyGlass.Models.JellyfinApi;
|
||||||
|
|
||||||
namespace JellyGlass.Services;
|
namespace JellyGlass.Services;
|
||||||
|
|
||||||
public class LibraryService : ILibraryService
|
public class LibraryService : ILibraryService
|
||||||
{
|
{
|
||||||
private IClientService _serverService;
|
private IClientService _clientService;
|
||||||
|
|
||||||
public LibraryService(IClientService serverService)
|
public LibraryService(IClientService serverService)
|
||||||
{
|
{
|
||||||
_serverService = serverService;
|
_clientService = serverService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Library[]> GetLibraries()
|
public async Task<Library[]> GetLibraries()
|
||||||
{
|
{
|
||||||
var clients = await _serverService.GetJellyfinClients();
|
var clients = await _clientService.GetJellyfinClients();
|
||||||
|
|
||||||
var libraries = new Dictionary<string, Library>();
|
var libraries = new Dictionary<string, Library>();
|
||||||
|
|
||||||
|
|
@ -21,7 +22,7 @@ public class LibraryService : ILibraryService
|
||||||
{
|
{
|
||||||
var clientLibraries = await client.GetInstanceLibraries();
|
var clientLibraries = await client.GetInstanceLibraries();
|
||||||
|
|
||||||
foreach (var library in clientLibraries.Items)
|
foreach (var library in clientLibraries)
|
||||||
{
|
{
|
||||||
if (library.Name == "Collections" || library.Name == "Playlists")
|
if (library.Name == "Collections" || library.Name == "Playlists")
|
||||||
{
|
{
|
||||||
|
|
@ -43,9 +44,73 @@ public class LibraryService : ILibraryService
|
||||||
return libraries.Values.ToArray();
|
return libraries.Values.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ItemDTO[]> GetItemsFromLibrary(string libraryName)
|
public async Task<Item> 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<ItemDTO[]> 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<ItemDTO>();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
dtos.Add(new ItemDTO(item, client.InstanceUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dtos.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Library[]> GetLibrariesFromServer(string serverId)
|
||||||
|
{
|
||||||
|
var client = await _clientService.GetClientForServerId(serverId);
|
||||||
|
|
||||||
|
var libraries = new Dictionary<string, Library>();
|
||||||
|
|
||||||
|
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<ItemDTO[]> GetChildrenFromItems(ItemDTO[] items)
|
// public async Task<ItemDTO[]> GetChildrenFromItems(ItemDTO[] items)
|
||||||
|
|
|
||||||
65
backend/src/Services/SearchService.cs
Normal file
65
backend/src/Services/SearchService.cs
Normal file
|
|
@ -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<ItemDTO[]> Search(string searchTerm, string serverId)
|
||||||
|
{
|
||||||
|
var client = await _clientService.GetClientForServerId(serverId);
|
||||||
|
|
||||||
|
var items = await client.GetItems(searchTerm: searchTerm);
|
||||||
|
|
||||||
|
var dtos = new List<ItemDTO>();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
dtos.Add(new ItemDTO(item, client.InstanceUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dtos.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ItemDTO[]> Search2(string searchTerm, string serverId)
|
||||||
|
{
|
||||||
|
var libraries = await _libraryService.GetLibrariesFromServer(serverId);
|
||||||
|
|
||||||
|
var foundItems = new List<ItemDTO>();
|
||||||
|
|
||||||
|
foreach (var library in libraries)
|
||||||
|
{
|
||||||
|
var found = await SearchLibraryForTerm(searchTerm, serverId, library);
|
||||||
|
|
||||||
|
foundItems.AddRange(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundItems.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ItemDTO[]> SearchLibraryForTerm(string searchTerm, string serverId, Library library)
|
||||||
|
{
|
||||||
|
var items = await _libraryService.GetItemsFromLibrary(library.Name, serverId);
|
||||||
|
|
||||||
|
var foundItems = new List<ItemDTO>();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item.Name.Contains(searchTerm, StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
foundItems.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundItems.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,8 @@ const ServerSearch = ({ searchTerm, server }: ServerSearchProps) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
search(searchTerm, server.id).then(results => {
|
search(searchTerm, server.id).then(results => {
|
||||||
setSearchResults(results);
|
setSearchResults(results);
|
||||||
|
}).catch(err => {
|
||||||
|
alert(err);
|
||||||
})
|
})
|
||||||
}, [searchTerm]);
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
|
@ -22,7 +24,7 @@ const ServerSearch = ({ searchTerm, server }: ServerSearchProps) => {
|
||||||
<Table striped bordered >
|
<Table striped bordered >
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{server.name}</th>
|
<th>{server.owner}'s server</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const ServerSearchResult = ({ searchResult, server }: ServerSearchResultProps) =
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={resultUrl} target="_blank" rel="noopener noreferrer">
|
<Link to={resultUrl} target="_blank" rel="noopener noreferrer">
|
||||||
<h3>{searchResult.name}</h3>
|
<h3>{searchResult.name} - {searchResult.productionYear}</h3>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,21 @@
|
||||||
|
import axios from "axios";
|
||||||
import type { Server } from "./Servers";
|
import type { Server } from "./Servers";
|
||||||
|
import { apiUrl } from "./api";
|
||||||
|
|
||||||
export interface SearchResult {
|
export interface SearchResult {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
type: SearchResultType;
|
type: SearchResultType;
|
||||||
episodes: number;
|
productionYear: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SearchResultType = "movie" | "tv show" | "music";
|
export type SearchResultType = "movie" | "tv show" | "music";
|
||||||
|
|
||||||
export const search = async (searchTerm: string, serverId: string): Promise<Array<SearchResult>> => {
|
export const search = async (searchTerm: string, serverId: string): Promise<Array<SearchResult>> => {
|
||||||
return [{
|
const response = await axios.get<Array<SearchResult>>(`${apiUrl}/search?searchTerm=${searchTerm}&serverId=${serverId}`);
|
||||||
name: "Test Result",
|
|
||||||
episodes: 0,
|
return response.data;
|
||||||
id: "awawa",
|
|
||||||
serverId: serverId,
|
|
||||||
type: "movie"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test result 2",
|
|
||||||
episodes: 0,
|
|
||||||
id: "awawa 2",
|
|
||||||
serverId: serverId,
|
|
||||||
type: "movie"
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getUrlForSearchResult = (result: SearchResult, server: Server): string => {
|
export const getUrlForSearchResult = (result: SearchResult, server: Server): string => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue