diff --git a/backend/src/Controllers/AuthController.cs b/backend/src/Controllers/AuthController.cs index 9bdf6bc..caa9c97 100644 --- a/backend/src/Controllers/AuthController.cs +++ b/backend/src/Controllers/AuthController.cs @@ -15,7 +15,7 @@ public class AuthController : ControllerBase _service = service; } - [HttpPost] + [HttpPost("login")] public async Task Authenticate([FromForm] string username, [FromForm] string password) { try @@ -29,4 +29,123 @@ public class AuthController : ControllerBase return Unauthorized(); } } + + [HttpGet] + public async Task GetLogins() + { + if (!await IsAdminAuthenticated()) + { + return Forbid(); + } + + var logins = await _service.GetLogins(); + + return Ok(logins); + } + + [HttpGet("{username}")] + public async Task GetLogin([FromRoute] string username) + { + if (!await IsAdminAuthenticated()) + { + return Forbid(); + } + + try + { + var login = await _service.GetLogin(username); + + return Ok(login); + } + catch (AuthNotFoundException) + { + return NotFound(); + } + } + + [HttpPost] + public async Task CreateLogin([FromForm] string username, [FromForm] string password) + { + if (!await IsAdminAuthenticated()) + { + return Forbid(); + } + + var newLogin = await _service.CreateLogin(username, password); + + return Ok(newLogin); + } + + [HttpPut] + public async Task UpdateLogin([FromForm] string username, [FromForm] string password, [FromForm] string newPassword) + { + if (!await IsAdminAuthenticated()) + { + return Forbid(); + } + + try + { + await _service.UpdateLoginOwnPassword(username, newPassword, password); + } + catch (AuthNotFoundException) + { + return BadRequest(); + } + catch (LoginFailedException) //the old password is wrong + { + return Unauthorized(); + } + + return Ok(); + } + + [HttpDelete] + public async Task DeleteLogin([FromForm] string username) + { + if (!await IsAdminAuthenticated()) + { + return Forbid(); + } + + try + { + var deletedLogin = await _service.DeleteLogin(username); + + return Ok(deletedLogin); + } + catch (AuthNotFoundException) + { + return NotFound(); + } + } + + private string? GetSessionToken() + { + return Request.Cookies["session"]; + } + + private async Task IsAuthenticated() + { + var sessionToken = GetSessionToken(); + + if (await _service.IsAuthenticated(sessionToken)) + { + return true; + } + + return false; + } + + private async Task IsAdminAuthenticated() + { + var sessionToken = GetSessionToken(); + + if (await _service.IsAuthenticated(sessionToken) && await _service.IsAdmin(sessionToken)) + { + return true; + } + + return false; + } } \ No newline at end of file diff --git a/backend/src/Controllers/SearchController.cs b/backend/src/Controllers/SearchController.cs index dbcd1cc..b635e49 100644 --- a/backend/src/Controllers/SearchController.cs +++ b/backend/src/Controllers/SearchController.cs @@ -1,3 +1,4 @@ +using JellyGlass.Exceptions; using JellyGlass.Services; using Microsoft.AspNetCore.Mvc; using System.Web; @@ -30,8 +31,15 @@ public class SearchController : ControllerBase } var decodedSearchTerm = HttpUtility.UrlDecode(searchTerm); - var results = await _service.Search(decodedSearchTerm, serverId); - return Ok(results); + try + { + var results = await _service.Search(decodedSearchTerm, serverId); + return Ok(results); + } + catch (ClientNotFoundException) + { + return NotFound(); + } } } \ No newline at end of file diff --git a/backend/src/Controllers/ServersController.cs b/backend/src/Controllers/ServersController.cs index 52f1baa..4c75bbe 100644 --- a/backend/src/Controllers/ServersController.cs +++ b/backend/src/Controllers/ServersController.cs @@ -1,3 +1,4 @@ +using JellyGlass.Exceptions; using JellyGlass.Services; using Microsoft.AspNetCore.Mvc; @@ -18,12 +19,10 @@ public class ServersController : ControllerBase _authService = authService; } - [HttpGet] - public async Task getServers() + [HttpGet("all")] + public async Task GetServers() { - var sessionToken = Request.Cookies["session"]; - - if (!await _authService.IsAuthenticated(sessionToken)) + if (!await IsAuthenticated()) { return Unauthorized(); } @@ -32,4 +31,98 @@ public class ServersController : ControllerBase return Ok(servers); } + + [HttpGet] + public async Task GetServer([FromQuery] int id) + { + try + { + var server = await _serverService.GetServerByID(id); + return Ok(server); + } + catch (ServerNotFoundException) + { + return NotFound(); + } + } + + [HttpPut] + public async Task UpdateServer([FromBody] int id, string owner, string url, string apiToken) + { + if (!await IsAdminAuthenticated()) + { + return Forbid(); + } + + try + { + var updatedServer = await _serverService.UpdateServer(id, owner, url, apiToken); + return Ok(updatedServer); + } + catch (ServerNotFoundException) + { + return NotFound(); + } + } + + [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) + { + if (!await IsAdminAuthenticated()) + { + return Forbid(); + } + + try + { + var deletedServer = await _serverService.DeleteServer(id); + return Ok(deletedServer); + } + catch (ServerNotFoundException) + { + return NotFound(); + } + } + + private string? GetSessionToken() + { + return Request.Cookies["session"]; + } + + private async Task IsAuthenticated() + { + var sessionToken = GetSessionToken(); + + if (await _authService.IsAuthenticated(sessionToken)) + { + return true; + } + + return false; + } + + private async Task IsAdminAuthenticated() + { + var sessionToken = GetSessionToken(); + + if (await _authService.IsAuthenticated(sessionToken) && await _authService.IsAdmin(sessionToken)) + { + return true; + } + + return false; + } } \ No newline at end of file diff --git a/backend/src/Exceptions/AuthRepositoryExceptions.cs b/backend/src/Exceptions/AuthRepositoryExceptions.cs index aebf688..040bfae 100644 --- a/backend/src/Exceptions/AuthRepositoryExceptions.cs +++ b/backend/src/Exceptions/AuthRepositoryExceptions.cs @@ -7,10 +7,16 @@ public class AuthRepositoryException : System.Exception public AuthRepositoryException(string message, System.Exception inner) : base(message, inner) { } } -[System.Serializable] public class AuthNotFoundException : AuthRepositoryException { public AuthNotFoundException() { } public AuthNotFoundException(string message) : base(message) { } public AuthNotFoundException(string message, System.Exception inner) : base(message, inner) { } +} + +public class AuthAlreadyExistsException : AuthRepositoryException +{ + public AuthAlreadyExistsException() { } + public AuthAlreadyExistsException(string message) : base(message) { } + public AuthAlreadyExistsException(string message, System.Exception inner) : base(message, inner) { } } \ No newline at end of file diff --git a/backend/src/Exceptions/ClientServiceExceptions.cs b/backend/src/Exceptions/ClientServiceExceptions.cs new file mode 100644 index 0000000..60c2289 --- /dev/null +++ b/backend/src/Exceptions/ClientServiceExceptions.cs @@ -0,0 +1,16 @@ +namespace JellyGlass.Exceptions; + +public class ClientServiceException : System.Exception +{ + public ClientServiceException() { } + public ClientServiceException(string message) : base(message) { } + public ClientServiceException(string message, System.Exception inner) : base(message, inner) { } +} + +[System.Serializable] +public class ClientNotFoundException : ClientServiceException +{ + public ClientNotFoundException() { } + public ClientNotFoundException(string message) : base(message) { } + public ClientNotFoundException(string message, System.Exception inner) : base(message, inner) { } +} \ No newline at end of file diff --git a/backend/src/Exceptions/ServerRepositoryExceptions.cs b/backend/src/Exceptions/ServerRepositoryExceptions.cs new file mode 100644 index 0000000..bd3c6ca --- /dev/null +++ b/backend/src/Exceptions/ServerRepositoryExceptions.cs @@ -0,0 +1,15 @@ +namespace JellyGlass.Exceptions; + +public class ServerRepositoryException : Exception +{ + public ServerRepositoryException() { } + public ServerRepositoryException(string message) : base(message) { } + public ServerRepositoryException(string message, Exception inner) : base(message, inner) { } +} + +public class ServerNotFoundException : ServerRepositoryException +{ + public ServerNotFoundException() { } + public ServerNotFoundException(string message) : base(message) { } + public ServerNotFoundException(string message, Exception inner) : base(message, inner) { } +} \ No newline at end of file diff --git a/backend/src/Migrations/20260305170928_admin.Designer.cs b/backend/src/Migrations/20260305170928_admin.Designer.cs new file mode 100644 index 0000000..a536e0d --- /dev/null +++ b/backend/src/Migrations/20260305170928_admin.Designer.cs @@ -0,0 +1,92 @@ +// +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("20260305170928_admin")] + partial class admin + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + 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/20260305170928_admin.cs b/backend/src/Migrations/20260305170928_admin.cs new file mode 100644 index 0000000..f3a5afc --- /dev/null +++ b/backend/src/Migrations/20260305170928_admin.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace JellyGlassBackend.Migrations +{ + /// + public partial class admin : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Id", + table: "Servers", + type: "INTEGER", + nullable: false, + oldClrType: typeof(string), + oldType: "TEXT") + .Annotation("Sqlite:Autoincrement", true); + + migrationBuilder.AddColumn( + name: "IsAdmin", + table: "Logins", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsAdmin", + table: "Logins"); + + migrationBuilder.AlterColumn( + name: "Id", + table: "Servers", + type: "TEXT", + nullable: false, + oldClrType: typeof(int), + oldType: "INTEGER") + .OldAnnotation("Sqlite:Autoincrement", true); + } + } +} diff --git a/backend/src/Migrations/DatabaseContextModelSnapshot.cs b/backend/src/Migrations/DatabaseContextModelSnapshot.cs index 6db48d8..4c4f81e 100644 --- a/backend/src/Migrations/DatabaseContextModelSnapshot.cs +++ b/backend/src/Migrations/DatabaseContextModelSnapshot.cs @@ -19,8 +19,9 @@ namespace JellyGlassBackend.Migrations modelBuilder.Entity("JellyGlass.Models.Server", b => { - b.Property("Id") - .HasColumnType("TEXT"); + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); b.Property("ApiToken") .IsRequired() @@ -48,6 +49,9 @@ namespace JellyGlassBackend.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + b.HasKey("Username"); b.ToTable("Logins"); diff --git a/backend/src/Models/LoginDTO.cs b/backend/src/Models/LoginDTO.cs deleted file mode 100644 index b38d465..0000000 --- a/backend/src/Models/LoginDTO.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace JellyGlass.Models; - -public class LoginDTO -{ - public required string Username { get; set; } - public required string Password { get; set; } -} \ No newline at end of file diff --git a/backend/src/Models/Server.cs b/backend/src/Models/Server.cs index e45a34e..2a22275 100644 --- a/backend/src/Models/Server.cs +++ b/backend/src/Models/Server.cs @@ -1,9 +1,14 @@ +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; - public string Id { get; set; } = string.Empty; + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } 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 4b6a2fe..585da9e 100644 --- a/backend/src/Models/ServerDTO.cs +++ b/backend/src/Models/ServerDTO.cs @@ -13,6 +13,7 @@ public class ServerDTO public string Owner { get; set; } = string.Empty; public string Url { get; set; } = string.Empty; - public string Id { 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/Models/UserLogin.cs b/backend/src/Models/UserLogin.cs index 10179f9..c7faefd 100644 --- a/backend/src/Models/UserLogin.cs +++ b/backend/src/Models/UserLogin.cs @@ -7,4 +7,5 @@ public class UserLogin [Key] public required string Username { get; set; } public required string HashedPassword { get; set; } + public required bool IsAdmin { get; set; } = false; } \ No newline at end of file diff --git a/backend/src/Models/UserLoginDTO.cs b/backend/src/Models/UserLoginDTO.cs new file mode 100644 index 0000000..dfe82e0 --- /dev/null +++ b/backend/src/Models/UserLoginDTO.cs @@ -0,0 +1,13 @@ +namespace JellyGlass.Models; + +public class UserLoginDTO +{ + public UserLoginDTO(UserLogin login) + { + Username = login.Username; + IsAdmin = login.IsAdmin; + } + + public string Username { get; set; } + public bool IsAdmin { get; set; } +} \ No newline at end of file diff --git a/backend/src/Repositories/IServerRepository.cs b/backend/src/Repositories/IServerRepository.cs index 4f07c87..d5f142b 100644 --- a/backend/src/Repositories/IServerRepository.cs +++ b/backend/src/Repositories/IServerRepository.cs @@ -5,5 +5,8 @@ namespace JellyGlass.Repositories; public interface IServerRepository { public Task GetServers(); - public Task GetServerById(string id); + public Task GetServerById(int id); + 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); } \ No newline at end of file diff --git a/backend/src/Repositories/IloginRepository.cs b/backend/src/Repositories/IloginRepository.cs index 37997ba..a3b5fb8 100644 --- a/backend/src/Repositories/IloginRepository.cs +++ b/backend/src/Repositories/IloginRepository.cs @@ -5,4 +5,9 @@ namespace JellyGlass.Repositories; public interface ILoginRepository { public Task GetUserLogin(string username); + public Task GetUserLogins(); + public Task CreateLogin(string username, string hashedPassword, bool isAdmin); + public Task ChangeUserPassword(string username, string hashedPassword); + public Task ChangeUserAdmin(string username, bool isAdmin); + public Task DeleteLogin(string username); } \ No newline at end of file diff --git a/backend/src/Repositories/LoginRepository.cs b/backend/src/Repositories/LoginRepository.cs index accfce7..1e23da1 100644 --- a/backend/src/Repositories/LoginRepository.cs +++ b/backend/src/Repositories/LoginRepository.cs @@ -24,4 +24,65 @@ public class LoginRepository : ILoginRepository return login; } + + public async Task GetUserLogins() + { + var logins = await _context.Logins.ToArrayAsync(); + + return logins; + } + + public async Task CreateLogin(string username, string hashedPassword, bool isAdmin) + { + if (_context.Logins.Where(l => l.Username == username).Any()) + { + throw new AuthAlreadyExistsException(username); + } + + var newLogin = new UserLogin() + { + Username = username, + HashedPassword = hashedPassword, + IsAdmin = isAdmin + }; + + await _context.Logins.AddAsync(newLogin); + await _context.SaveChangesAsync(); + + return newLogin; + } + + public async Task ChangeUserPassword(string username, string hashedPassword) + { + var login = await GetUserLogin(username); + + login.HashedPassword = hashedPassword; + + _context.Logins.Update(login); + await _context.SaveChangesAsync(); + + return login; + } + + public async Task ChangeUserAdmin(string username, bool isAdmin) + { + var login = await GetUserLogin(username); + + login.IsAdmin = isAdmin; + + _context.Logins.Update(login); + await _context.SaveChangesAsync(); + + return login; + } + + public async Task DeleteLogin(string username) + { + var login = await GetUserLogin(username); + + _context.Logins.Remove(login); + await _context.SaveChangesAsync(); + + return login; + } } \ No newline at end of file diff --git a/backend/src/Repositories/ServerRepository.cs b/backend/src/Repositories/ServerRepository.cs index e2a9558..c6ceda9 100644 --- a/backend/src/Repositories/ServerRepository.cs +++ b/backend/src/Repositories/ServerRepository.cs @@ -1,3 +1,4 @@ +using JellyGlass.Exceptions; using JellyGlass.Models; using Microsoft.EntityFrameworkCore; @@ -19,10 +20,55 @@ public class ServerRepository : IServerRepository return servers; } - public async Task GetServerById(string id) + public async Task GetServerById(int id) { var server = await _context.Servers.FirstOrDefaultAsync(s => s.Id == id); + if (server == null) + { + throw new ServerNotFoundException(); + } + + return server; + } + + public async Task CreateServer(string owner, string url, string apiToken) + { + + var newServer = new Server() + { + ApiToken = apiToken, + Url = url, + Owner = owner + }; + + await _context.Servers.AddAsync(newServer); + await _context.SaveChangesAsync(); + + return newServer; + } + + public async Task UpdateServer(int id, string owner, string url, string apiToken) + { + var server = await GetServerById(id); + + server.Owner = owner; + server.Url = url; + server.ApiToken = apiToken; + + _context.Servers.Update(server); + await _context.SaveChangesAsync(); + + return server; + } + + public async Task DeleteServer(int id) + { + var server = await GetServerById(id); + + _context.Servers.Remove(server); + await _context.SaveChangesAsync(); + return server; } } \ No newline at end of file diff --git a/backend/src/Repositories/SessionRepository.cs b/backend/src/Repositories/SessionRepository.cs index bec189d..ace3618 100644 --- a/backend/src/Repositories/SessionRepository.cs +++ b/backend/src/Repositories/SessionRepository.cs @@ -30,7 +30,7 @@ public class SessionRepository : ISessionRepository public async Task GetUserSession(string sessionToken) { - var session = await _context.Sessions.FirstOrDefaultAsync(s => s.SessionToken == sessionToken); + var session = await _context.Sessions.Include(s => s.Login).FirstOrDefaultAsync(s => s.SessionToken == sessionToken); return session; } diff --git a/backend/src/Services/AuthService.cs b/backend/src/Services/AuthService.cs index 7b98090..290008b 100644 --- a/backend/src/Services/AuthService.cs +++ b/backend/src/Services/AuthService.cs @@ -49,19 +49,87 @@ public class AuthService : IAuthService var session = await _sessionRepo.GetUserSession(sessionToken); - if (session == null) + if (session == null) //session doesn't exist { return false; } - else if (session.ExpiresOn < DateTime.Now) + else if (session.ExpiresOn < DateTime.Now) //session has timed out { await _sessionRepo.DeleteSession(sessionToken); return false; } - else + else //session exists and hasn't timed out { await _sessionRepo.RefreshSessionExpiry(sessionToken); return true; } } + + public async Task IsAdmin(string sessionToken) + { + var session = await _sessionRepo.GetUserSession(sessionToken); + + if (session == null) + { + throw new SessionNotFoundException(); + } + + return session.Login.IsAdmin; + } + + public async Task GetLogins() + { + var logins = await _loginRepo.GetUserLogins(); + + var loginDTOs = new List(); + + foreach (var login in logins) + { + loginDTOs.Add(new UserLoginDTO(login)); + } + + return loginDTOs.ToArray(); + } + + public async Task GetLogin(string username) + { + var login = await _loginRepo.GetUserLogin(username); + + return new UserLoginDTO(login); + } + + public async Task CreateLogin(string username, string password) + { + var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password); + + var newLogin = await _loginRepo.CreateLogin(username, hashedPassword, false); + + return new UserLoginDTO(newLogin); + } + + public async Task UpdateLoginOwnPassword(string username, string newPassword, string oldPassword) + { + var login = await _loginRepo.GetUserLogin(username); + + if (!BCrypt.Net.BCrypt.Verify(oldPassword, login.HashedPassword)) + { + throw new LoginFailedException(); + } + + await UpdateLoginPassword(username, newPassword); + } + + public async Task UpdateLoginPassword(string username, string newPassword) + { + var hashedNewPassword = BCrypt.Net.BCrypt.HashPassword(newPassword); + + await _loginRepo.ChangeUserPassword(username, hashedNewPassword); + } + + public async Task DeleteLogin(string username) + { + var deletedLogin = await _loginRepo.DeleteLogin(username); + + return new UserLoginDTO(deletedLogin); + } } \ No newline at end of file diff --git a/backend/src/Services/IAuthService.cs b/backend/src/Services/IAuthService.cs index c69b5b2..2c0549e 100644 --- a/backend/src/Services/IAuthService.cs +++ b/backend/src/Services/IAuthService.cs @@ -6,4 +6,11 @@ public interface IAuthService { public Task AuthenticateUser(string username, string password); public Task IsAuthenticated(string? sessionToken); + public Task IsAdmin(string sessionToken); + 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 UpdateLoginPassword(string username, string newPassword); + public Task DeleteLogin(string username); } \ No newline at end of file diff --git a/backend/src/Services/IServerService.cs b/backend/src/Services/IServerService.cs index c0e034c..0729725 100644 --- a/backend/src/Services/IServerService.cs +++ b/backend/src/Services/IServerService.cs @@ -7,4 +7,8 @@ namespace JellyGlass.Services; public interface IServerService { public Task GetServers(); + public Task GetServerByID(int id); + 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); } \ No newline at end of file diff --git a/backend/src/Services/ServerService.cs b/backend/src/Services/ServerService.cs index 962ad15..1d1dad2 100644 --- a/backend/src/Services/ServerService.cs +++ b/backend/src/Services/ServerService.cs @@ -28,11 +28,9 @@ public class ServerService : IServerService { _logger.LogInformation($"ID for server {client.InstanceUrl} is {client.ID}"); - var dto = new ServerDTO(); var server = servers.First(s => s.Url == client.InstanceUrl); - dto.Id = client.ID; - dto.Url = client.InstanceUrl; - dto.Owner = server.Owner; + var dto = new ServerDTO(server); + dto.JellyfinServerID = client.ID; dtos.Add(dto); } @@ -51,17 +49,31 @@ public class ServerService : IServerService return dtos.ToArray(); } - public async Task GetServers2() + public async Task GetServerByID(int id) { - var servers = await _repository.GetServers(); + var server = await _repository.GetServerById(id); - var dtos = new List(); + return new ServerDTO(server); + } - foreach (var s in servers) - { - dtos.Add(new ServerDTO(s)); - } + public async Task CreateServer(string owner, string url, string apiToken) + { + var newServer = await _repository.CreateServer(owner, url, apiToken); - return dtos.ToArray(); + return new ServerDTO(newServer); + } + + public async Task UpdateServer(int id, string owner, string url, string apiToken) + { + var updatedServer = await _repository.UpdateServer(id, owner, url, apiToken); + + return new ServerDTO(updatedServer); + } + + public async Task DeleteServer(int id) + { + var deletedServer = await _repository.DeleteServer(id); + + return new ServerDTO(deletedServer); } } \ No newline at end of file diff --git a/frontend/src/Components/ServerSearch/ServerSearch.tsx b/frontend/src/Components/ServerSearch/ServerSearch.tsx index cce7991..843ab91 100644 --- a/frontend/src/Components/ServerSearch/ServerSearch.tsx +++ b/frontend/src/Components/ServerSearch/ServerSearch.tsx @@ -13,7 +13,7 @@ const ServerSearch = ({ searchTerm, server }: ServerSearchProps) => { const [searchResults, setSearchResults] = useState>(); useEffect(() => { - search(searchTerm, server.id).then(results => { + search(searchTerm, server.jellyfinServerID).then(results => { setSearchResults(results); }).catch(err => { setSearchResults([]); diff --git a/frontend/src/Lib/Auth.ts b/frontend/src/Lib/Auth.ts index d9691f5..5c0066a 100644 --- a/frontend/src/Lib/Auth.ts +++ b/frontend/src/Lib/Auth.ts @@ -7,7 +7,7 @@ export interface Session { } export const logIn = async (username: string, password: string) => { - const response = await axios.post(`${apiUrl}/auth`, `username=${encodeURI(username)}&password=${encodeURI(password)}`); + const response = await axios.post(`${apiUrl}/auth/login`, `username=${encodeURI(username)}&password=${encodeURI(password)}`); return response.data; } \ No newline at end of file diff --git a/frontend/src/Lib/Servers.ts b/frontend/src/Lib/Servers.ts index 11267d0..867194e 100644 --- a/frontend/src/Lib/Servers.ts +++ b/frontend/src/Lib/Servers.ts @@ -3,7 +3,8 @@ import { apiUrl } from "./api"; export interface Server { name?: string; - id: string; + id: number; + jellyfinServerID: string; errored: boolean; owner: string; url: string; @@ -11,7 +12,7 @@ export interface Server { export const getServerList = async (): Promise> => { console.log("fetching server list"); - const response = await axios.get>(`${apiUrl}/servers`, { withCredentials: true }); + const response = await axios.get>(`${apiUrl}/servers/all`, { withCredentials: true }); console.log(response);