don't look at me

This commit is contained in:
Fishandchips321 2025-12-24 18:23:06 +00:00
parent e9f444e5b4
commit cedbad8fba
56 changed files with 1111 additions and 294 deletions

View file

@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View file

@ -1 +0,0 @@
namespace JellyGlass.Application.Services;

View file

@ -0,0 +1,51 @@
using JellyGlass.Models;
using JellyGlass.Services;
using Microsoft.AspNetCore.Mvc;
namespace JellyGlass.Controllers;
[ApiController]
[Route("/api/[controller]")]
public class LibraryController : ControllerBase
{
private ILogger<LibraryController> _logger;
private ILibraryService _service;
public LibraryController(ILogger<LibraryController> logger, ILibraryService service)
{
_logger = logger;
_service = service;
}
[HttpGet()]
public async Task<IActionResult> GetLibrares()
{
var libraries = await _service.GetLibraries();
return Ok(libraries);
}
[HttpGet("{libraryName}")]
public async Task<IActionResult> GetLibraryItems([FromRoute] string libraryName)
{
throw new NotImplementedException();
}
// [HttpGet("{libraryName}/Item/{itemName}")]
// public async Task<IActionResult> GetLibraryItem([FromRoute] string libraryName, string itemName)
// {
// }
// [HttpGet("TvShows/{seriesName}")]
// public async Task<IActionResult> GetSeasonsForTvSeries([FromRoute] string seriesName)
// {
// }
// [HttpGet("TvShows/{seriesName}/Season/{seasonName}")]
// public async Task<IActionResult> GetEpisodesForTvSeriesSeason([FromRoute] string seriesName, string seasonName)
// {
// }
}

View file

@ -1,9 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View file

@ -1,9 +0,0 @@
namespace JellyGlass.Core.Entities;
public class Item
{
public string ID { get; set; }
public string Name { get; set; }
public Uri ThumbnailUrl { get; set; }
public Uri Url { get; set; }
}

View file

@ -1,8 +0,0 @@
namespace JellyGlass.Core.Entities;
public class Library
{
public string ID { get; set; }
public string Name { get; set; }
public Uri ThumbnailUrl { get; set; }
}

View file

@ -1,7 +0,0 @@
namespace JellyGlass.Core.Entities;
public class Server
{
public string Owner { get; set; }
public Uri Url { get; set; }
}

View file

@ -1,8 +0,0 @@
using JellyGlass.Core.Entities;
namespace JellyGlass.Core.Interfaces;
public interface IItemRepository
{
public Task<Item[]> GetItemsFromLibrary(string libraryId);
}

View file

@ -1,8 +0,0 @@
using JellyGlass.Core.Entities;
namespace JellyGlass.Core.Interfaces;
public interface IItemService
{
public Task<Item[]> GetItemsFromLibrary(string libraryId);
}

View file

@ -1,8 +0,0 @@
using JellyGlass.Core.Entities;
namespace JellyGlass.Core.Interfaces;
public interface ILibraryRepository
{
public Task<Library[]> GetLibraries();
}

View file

@ -1,8 +0,0 @@
using JellyGlass.Core.Entities;
namespace JellyGlass.Core.Interfaces;
public interface ILibraryService
{
public Task<Library[]> GetLibraries();
}

View file

@ -1,8 +0,0 @@
using JellyGlass.Core.Entities;
namespace JellyGlass.Core.Interfaces;
public interface IServerService
{
public Task<Server[]> GetServers();
}

View file

@ -0,0 +1,9 @@
namespace JellyGlass.Exceptions;
[Serializable]
public class JellyfinApiClientException : Exception
{
public JellyfinApiClientException() { }
public JellyfinApiClientException(string message) : base(message) { }
public JellyfinApiClientException(string message, Exception inner) : base(message, inner) { }
}

View file

@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\Application\Application.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View file

@ -1,4 +0,0 @@
using JellyGlass.Core.Entities;
using JellyGlass.Core.Interfaces;
namespace JellyGlass.Infrastructure.Repositories;

View file

@ -1,12 +0,0 @@
using JellyGlass.Core.Entities;
using JellyGlass.Core.Interfaces;
namespace JellyGlass.Infrastructure.Repositories;
public class LibraryRepository : ILibraryRepository
{
public Task<Library[]> GetLibraries()
{
throw new NotImplementedException();
}
}

View file

@ -1,4 +0,0 @@
using JellyGlass.Core.Entities;
using JellyGlass.Core.Interfaces;
namespace JellyGlass.Infrastructure.Repositories;

View file

@ -1 +0,0 @@
namespace JellyGlass.Infrastructure.Services;

View file

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
</ItemGroup>
</Project>

Binary file not shown.

View file

@ -1,40 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi", "WebApi\WebApi.csproj", "{870696BA-371D-455E-B39A-7B3B14FDE62D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "Application\Application.csproj", "{5C6D5937-49DA-4758-9AE5-C1290975DBB4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{DFE2D8D3-3174-44D9-AC91-C129D208A37B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{7BA1276D-3AF5-4E09-B996-2D91556F8939}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{870696BA-371D-455E-B39A-7B3B14FDE62D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{870696BA-371D-455E-B39A-7B3B14FDE62D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{870696BA-371D-455E-B39A-7B3B14FDE62D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{870696BA-371D-455E-B39A-7B3B14FDE62D}.Release|Any CPU.Build.0 = Release|Any CPU
{5C6D5937-49DA-4758-9AE5-C1290975DBB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C6D5937-49DA-4758-9AE5-C1290975DBB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C6D5937-49DA-4758-9AE5-C1290975DBB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C6D5937-49DA-4758-9AE5-C1290975DBB4}.Release|Any CPU.Build.0 = Release|Any CPU
{DFE2D8D3-3174-44D9-AC91-C129D208A37B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFE2D8D3-3174-44D9-AC91-C129D208A37B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFE2D8D3-3174-44D9-AC91-C129D208A37B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFE2D8D3-3174-44D9-AC91-C129D208A37B}.Release|Any CPU.Build.0 = Release|Any CPU
{7BA1276D-3AF5-4E09-B996-2D91556F8939}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7BA1276D-3AF5-4E09-B996-2D91556F8939}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BA1276D-3AF5-4E09-B996-2D91556F8939}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BA1276D-3AF5-4E09-B996-2D91556F8939}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,50 @@
// <auto-generated />
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("20251224181902_initial")]
partial class initial
{
/// <inheritdoc />
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<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("Owner")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Servers");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace JellyGlassBackend.Migrations
{
/// <inheritdoc />
public partial class initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Servers",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Owner = table.Column<string>(type: "TEXT", nullable: false),
Url = table.Column<string>(type: "TEXT", nullable: false),
Password = table.Column<string>(type: "TEXT", nullable: false),
Username = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Servers", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Servers");
}
}
}

View file

@ -0,0 +1,47 @@
// <auto-generated />
using JellyGlass.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace JellyGlassBackend.Migrations
{
[DbContext(typeof(DatabaseContext))]
partial class DatabaseContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.11");
modelBuilder.Entity("JellyGlass.Models.Server", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("Owner")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Servers");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,29 @@
using JellyGlass.Models.JellyfinApi;
namespace JellyGlass.Models;
public class ItemDTO
{
public ItemDTO() { }
public ItemDTO(Item item, string ServerUrl)
{
ID = item.Id;
Name = item.Name;
ServerID = item.ServerId;
this.ServerUrl = ServerUrl;
Type = item.Type;
Index = item.IndexNumber;
ParentId = item.ParentId;
ThumbnailUrl = $"{this.ServerUrl}/Items/{ID}/Images/Primary";
}
public string ID { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string ServerID { get; set; } = string.Empty;
public string ServerUrl { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty;
public int? Index { get; set; }
public string? ParentId { get; set; }
public string? ThumbnailUrl { get; set; }
}

View file

@ -0,0 +1,14 @@
namespace JellyGlass.Models.JellyfinApi;
public class AuthResponse
{
public required User User { get; set; }
public required string AccessToken { get; set; }
public required string ServerId { get; set; }
}
public class User
{
public required string Id { get; set; }
}

View file

@ -0,0 +1,24 @@
namespace JellyGlass.Models.JellyfinApi;
public class Item
{
public string Name { get; set; } = string.Empty;
public string ServerId { get; set; } = string.Empty;
public string Id { get; set; } = string.Empty;
public DateTime DateCreated { get; set; }
public bool CanDownload { get; set; }
public string SortName { get; set; } = string.Empty;
public int? IndexNumber { get; set; }
public bool IsFolder { get; set; } //Whether item has children or not
public string? ParentId { get; set; }
public int? ParentIndexNumber { get; set; }
public string? PremiereDate { get; set; }
public int? ProductionYear { get; set; }
public string? SeriesName { get; set; }
public string? SeriesId { get; set; }
public string? SeasonId { get; set; }
public string? SeasonName { get; set; }
public string Type { get; set; } = string.Empty;
public double PrimaryImageAspectRatio { get; set; }
public string? CollectionType { get; set; }
}

View file

@ -0,0 +1,8 @@
namespace JellyGlass.Models.JellyfinApi;
public class ItemResponse
{
public List<Item> Items { get; set; } = [];
public int TotalRecordCount { get; set; }
public int StartIndex { get; set; }
}

View file

@ -0,0 +1,8 @@
namespace JellyGlass.Models;
public class Library
{
public string Name { get; set; } = string.Empty;
public string ThumbnailUrl { get; set; } = string.Empty;
}

View file

@ -0,0 +1,10 @@
namespace JellyGlass.Models;
public class Server
{
public string Owner { get; set; } = string.Empty;
public string Url { get; set; } = string.Empty;
public string Id { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
}

41
backend/src/Program.cs Normal file
View file

@ -0,0 +1,41 @@
using JellyGlass.Repositories;
using JellyGlass.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
builder.Services.AddControllers();
builder.Configuration.GetSection("TestingLogin");
string dbConnectionString;
if (builder.Environment.IsDevelopment())
{
dbConnectionString = "Data Source=JellyGlass-test.db;";
}
else
{
dbConnectionString = "Data Source./JellyGlass.db;";
}
builder.Services.AddSqlite<DatabaseContext>(dbConnectionString);
builder.Services.AddTransient<ILibraryService, LibraryService>();
builder.Services.AddTransient<IServerRepository, ServerRepository>();
builder.Services.AddScoped<IServerService, ServerService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
app.MapControllers();
app.Run();

View file

@ -5,7 +5,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": false,
"applicationUrl": "http://localhost:5152", "applicationUrl": "http://localhost:5092",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
@ -14,7 +14,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": false,
"applicationUrl": "https://localhost:7106;http://localhost:5152", "applicationUrl": "https://localhost:7226;http://localhost:5092",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

View file

@ -0,0 +1,15 @@
using JellyGlass.Models;
using Microsoft.EntityFrameworkCore;
namespace JellyGlass.Repositories;
public class DatabaseContext : DbContext
{
public DatabaseContext(DbContextOptions options)
: base(options)
{
}
public DbSet<Server> Servers { get; set; }
}

View file

@ -1,6 +1,6 @@
using JellyGlass.Core.Entities; using JellyGlass.Models;
namespace JellyGlass.Core.Interfaces; namespace JellyGlass.Repositories;
public interface IServerRepository public interface IServerRepository
{ {

View file

@ -0,0 +1,149 @@
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using JellyGlass.Exceptions;
using JellyGlass.Models;
using JellyGlass.Models.JellyfinApi;
namespace JellyGlass.Repositories;
public class JellyfinApiClient
{
private string _apiKey = string.Empty;
public readonly string InstanceUrl;
private readonly HttpClient _client;
private readonly string _username, _password;
public JellyfinApiClient(string instanceUrl, string username, string password)
{
InstanceUrl = instanceUrl;
_client = new HttpClient();
_client.DefaultRequestHeaders.Clear();
_username = username;
_password = password;
}
public async Task<ItemResponse> GetInstanceLibraries()
{
try
{
var request = new HttpRequestMessage(HttpMethod.Get, $"{InstanceUrl}/Library/MediaFolders");
var response = await MakeRequest(request);
response.EnsureSuccessStatusCode();
var apiResponse = await response.Content.ReadFromJsonAsync<ItemResponse>();
return apiResponse!;
}
catch (HttpRequestException e)
{
throw new JellyfinApiClientException(e.Message);
}
}
public async Task<ItemResponse> GetItemChildren(string itemId)
{
try
{
var request = new HttpRequestMessage(HttpMethod.Get, $"{InstanceUrl}/items?ParentId={itemId}");
var response = await MakeRequest(request);
response.EnsureSuccessStatusCode();
var apiResponse = await response.Content.ReadFromJsonAsync<ItemResponse>();
return apiResponse!;
}
catch (HttpRequestException e)
{
throw new JellyfinApiClientException(e.Message);
}
}
public async Task<ItemResponse> GetItems(string searchTerm = "", string years = "", string itemTypes = "", string limit = "", string parentId = "")
{
var query = new Dictionary<string, string>();
if (searchTerm != String.Empty)
{
query.Add("SearchTerm", searchTerm);
}
throw new NotImplementedException();
}
public async Task Authenticate()
{
var request = new HttpRequestMessage(HttpMethod.Post, $"{InstanceUrl}/Users/AuthenticateByName");
request.Headers.Authorization = new AuthenticationHeaderValue("MediaBrowser", GetAuthHeader());
var body = new
{
Username = _username,
Pw = _password
};
request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
try
{
var response = await _client.SendAsync(request);
response.EnsureSuccessStatusCode();
var authResponse = await response.Content.ReadFromJsonAsync<AuthResponse>();
_apiKey = authResponse!.AccessToken;
}
catch (HttpRequestException e)
{
//TODO: What to do on an exception
throw new JellyfinApiClientException(e.Message);
}
}
private async Task<HttpResponseMessage> MakeRequest(HttpRequestMessage request)
{
request.Headers.Authorization = new AuthenticationHeaderValue("MediaBrowser", GetAuthHeader());
HttpResponseMessage response;
try
{
response = await _client.SendAsync(request);
}
catch (HttpRequestException e)
{
if (e.StatusCode == HttpStatusCode.Unauthorized)
{
await Authenticate();
request.Headers.Authorization = new AuthenticationHeaderValue("MediaBrowser", GetAuthHeader());
response = await _client.SendAsync(request);
}
else
{
throw new JellyfinApiClientException(e.Message);
}
}
return response;
}
private string GetAuthHeader()
{
var header = "Client=Test, Device=Test, DeviceId=Test, Version=1";
if (_apiKey != String.Empty)
{
header += ", Token=" + _apiKey;
}
return header;
}
}

View file

@ -0,0 +1,21 @@
using JellyGlass.Models;
using Microsoft.EntityFrameworkCore;
namespace JellyGlass.Repositories;
public class ServerRepository : IServerRepository
{
private DatabaseContext _context;
public ServerRepository(DatabaseContext context)
{
_context = context;
}
public async Task<Server[]> GetServers()
{
var servers = await _context.Servers.ToArrayAsync();
return servers;
}
}

View file

@ -0,0 +1,14 @@
using JellyGlass.Models;
namespace JellyGlass.Services;
public interface ILibraryService
{
public Task<Library[]> GetLibraries();
public Task<ItemDTO[]> GetItemsFromLibrary(string libraryName);
// public Task<ItemDTO[]> GetChildrenFromItems(ItemDTO[] items);
// public Task<ItemDTO[]> GetItemsByName(string name, string itemType);
// public Task<ItemDTO[]> GetItemsByType(string itemType);
}

View file

@ -0,0 +1,10 @@
using JellyGlass.Repositories;
namespace JellyGlass.Services;
public interface IServerService
{
public Task<JellyfinApiClient[]> GetJellyfinClients();
// public JellyfinApiClient GetClientForServer(string url);
// public JellyfinApiClient GetClientForServerId(string serverId);
}

View file

@ -0,0 +1,74 @@
using JellyGlass.Models;
namespace JellyGlass.Services;
public class LibraryService : ILibraryService
{
private IServerService _serverService;
public LibraryService(IServerService serverService)
{
_serverService = serverService;
}
public async Task<Library[]> GetLibraries()
{
var clients = await _serverService.GetJellyfinClients();
var libraries = new Dictionary<string, Library>();
foreach (var client in clients)
{
var clientLibraries = await client.GetInstanceLibraries();
foreach (var library in clientLibraries.Items)
{
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[]> GetItemsFromLibrary(string libraryName)
{
throw new NotImplementedException();
}
// public async Task<ItemDTO[]> GetChildrenFromItems(ItemDTO[] items)
// {
// var children = new List<ItemDTO>();
// foreach (var item in items)
// {
// var client = _serverService.GetClientForServerId(item.ServerID);
// var itemChildren = await client.GetItemChildren(item.ID);
// foreach (var child in itemChildren.Items)
// {
// children.Add(new ItemDTO(child, client.InstanceUrl));
// }
// }
// return children.ToArray();
// }
// public async Task<ItemDTO> GetItemsByName(string name, string itemType)
// {
// }
// public async Task<ItemDTO> GetItemsByType(string itemType)
// {
// }
}

View file

@ -0,0 +1,52 @@
using JellyGlass.Exceptions;
using JellyGlass.Models;
using JellyGlass.Repositories;
namespace JellyGlass.Services;
public class ServerService : IServerService
{
private IServerRepository _repository;
private static JellyfinApiClient[] _clients = [];
private ILogger<ServerService> _logger;
public ServerService(IServerRepository repository, ILogger<ServerService> logger)
{
_repository = repository;
_logger = logger;
}
public async Task<JellyfinApiClient[]> GetJellyfinClients()
{
if (!_clients.Any())
{
await LoadClients();
}
return _clients;
}
private async Task LoadClients()
{
var servers = await _repository.GetServers();
var clients = new List<JellyfinApiClient>();
foreach (var server in servers)
{
var client = new JellyfinApiClient(server.Url, server.Username, server.Password);
try
{
await client.Authenticate();
}
catch (JellyfinApiClientException e)
{
}
clients.Add(client);
}
_clients = clients.ToArray();
}
}

View file

@ -1,25 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using JellyGlass.Core.Interfaces;
namespace JellyGlass.WebApi.Controllers;
[ApiController]
[Route("[controller]")]
public class ItemController : ControllerBase
{
private ILogger<ItemController> _logger;
private IItemService _service;
public ItemController(ILogger<ItemController> logger, IItemService service)
{
_service = service;
_logger = logger;
}
public async Task<IActionResult> GetItemsForLibrary(string libraryId)
{
var items = await _service.GetItemsFromLibrary(libraryId);
return Ok(items);
}
}

View file

@ -1,26 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using JellyGlass.Core.Interfaces;
namespace JellyGlass.WebApi.Controllers;
[ApiController]
[Route("[controller]")]
public class LibraryController : ControllerBase
{
private ILogger<LibraryController> _logger;
private ILibraryService _service;
public LibraryController(ILogger<LibraryController> logger, ILibraryService service)
{
_logger = logger;
_service = service;
}
[HttpGet]
public async Task<IActionResult> GetLibraries()
{
var libraries = await _service.GetLibraries();
return Ok(libraries);
}
}

View file

@ -1,25 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using JellyGlass.Core.Interfaces;
namespace JellyGlass.WebApi.Controllers;
[ApiController]
[Route("[controller]")]
public class ServerController : ControllerBase
{
private ILogger<ServerController> _logger;
private IServerService _service;
public ServerController(ILogger<ServerController> logger, IServerService service)
{
_logger = logger;
_service = service;
}
public async Task<IActionResult> GetServers()
{
var servers = await _service.GetServers();
return Ok(servers);
}
}

View file

@ -1,19 +0,0 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
app.MapControllers();
app.Run();

View file

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" />
<ProjectReference Include="..\Core\Core.csproj" />
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>
</Project>

View file

@ -1,6 +0,0 @@
@WebApi_HostAddress = http://localhost:5152
GET {{WebApi_HostAddress}}/weatherforecast/
Accept: application/json
###

View file

@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

6
backend/src/src.http Normal file
View file

@ -0,0 +1,6 @@
@src_HostAddress = http://localhost:5092
GET {{src_HostAddress}}/weatherforecast/
Accept: application/json
###

View file

@ -0,0 +1,247 @@
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.
.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.
.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.
.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.
.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.
.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.
.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.
.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
For more information on Execution Policies:
https://go.microsoft.com/fwlink/?LinkID=135170
#>
Param(
[Parameter(Mandatory = $false)]
[String]
$VenvDir,
[Parameter(Mandatory = $false)]
[String]
$Prompt
)
<# Function declarations --------------------------------------------------- #>
<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.
.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.
#>
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values
# The prior prompt:
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
}
# The prior PYTHONHOME:
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
}
# The prior PATH:
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
}
# Just remove the VIRTUAL_ENV altogether:
if (Test-Path -Path Env:VIRTUAL_ENV) {
Remove-Item -Path env:VIRTUAL_ENV
}
# Just remove VIRTUAL_ENV_PROMPT altogether.
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
}
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
}
# Leave deactivate function in the global namespace if requested:
if (-not $NonDestructive) {
Remove-Item -Path function:deactivate
}
}
<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.
If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.
.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
[String]
$ConfigDir
) {
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
# An empty map will be returned if no config file is found.
$pyvenvConfig = @{ }
if ($pyvenvConfigPath) {
Write-Verbose "File exists, parse `key = value` lines"
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
$pyvenvConfigContent | ForEach-Object {
$keyval = $PSItem -split "\s*=\s*", 2
if ($keyval[0] -and $keyval[1]) {
$val = $keyval[1]
# Remove extraneous quotations around a string value.
if ("'""".Contains($val.Substring(0, 1))) {
$val = $val.Substring(1, $val.Length - 2)
}
$pyvenvConfig[$keyval[0]] = $val
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
}
}
}
return $pyvenvConfig
}
<# Begin Activate script --------------------------------------------------- #>
# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
Write-Verbose "VenvDir=$VenvDir"
}
# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
$Prompt = $pyvenvCfg['prompt'];
}
else {
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
$Prompt = Split-Path -Path $venvDir -Leaf
}
}
Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"
# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
Write-Verbose "Setting prompt to '$Prompt'"
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT { "" }
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
function global:prompt {
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
_OLD_VIRTUAL_PROMPT
}
$env:VIRTUAL_ENV_PROMPT = $Prompt
}
# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
Remove-Item -Path Env:PYTHONHOME
}
# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

View file

@ -0,0 +1,70 @@
# This file must be used with "source bin/activate" *from bash*
# You cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# Call hash to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
hash -r 2> /dev/null
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
# on Windows, a path can contain colons and backslashes and has to be converted:
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
# transform D:\path\to\venv to /d/path/to/venv on MSYS
# and to /cygdrive/d/path/to/venv on Cygwin
export VIRTUAL_ENV=$(cygpath /home/fox/Documents/programming/Projects/JellyGlass/pythonBackend/venv)
else
# use the path as-is
export VIRTUAL_ENV=/home/fox/Documents/programming/Projects/JellyGlass/pythonBackend/venv
fi
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/"bin":$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1='(venv) '"${PS1:-}"
export PS1
VIRTUAL_ENV_PROMPT='(venv) '
export VIRTUAL_ENV_PROMPT
fi
# Call hash to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
hash -r 2> /dev/null

View file

@ -0,0 +1,27 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV /home/fox/Documents/programming/Projects/JellyGlass/pythonBackend/venv
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
set prompt = '(venv) '"$prompt"
setenv VIRTUAL_ENV_PROMPT '(venv) '
endif
alias pydoc python -m pydoc
rehash

View file

@ -0,0 +1,69 @@
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
# (https://fishshell.com/). You cannot run it directly.
function deactivate -d "Exit virtual environment and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
set -e _OLD_FISH_PROMPT_OVERRIDE
# prevents error when using nested fish instances (Issue #93858)
if functions -q _old_fish_prompt
functions -e fish_prompt
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
end
set -e VIRTUAL_ENV
set -e VIRTUAL_ENV_PROMPT
if test "$argv[1]" != "nondestructive"
# Self-destruct!
functions -e deactivate
end
end
# Unset irrelevant variables.
deactivate nondestructive
set -gx VIRTUAL_ENV /home/fox/Documents/programming/Projects/JellyGlass/pythonBackend/venv
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
# Unset PYTHONHOME if set.
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# Save the current fish_prompt function as the function _old_fish_prompt.
functions -c fish_prompt _old_fish_prompt
# With the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command.
set -l old_status $status
# Output the venv prompt; color taken from the blue of the Python logo.
printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal)
# Restore the return status of the previous command.
echo "exit $old_status" | .
# Output the original/"old" prompt.
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
set -gx VIRTUAL_ENV_PROMPT '(venv) '
end