diff --git a/back/src/Kyoo.Authentication/Views/AuthApi.cs b/back/src/Kyoo.Authentication/Views/AuthApi.cs index 2464f810..92a05409 100644 --- a/back/src/Kyoo.Authentication/Views/AuthApi.cs +++ b/back/src/Kyoo.Authentication/Views/AuthApi.cs @@ -302,6 +302,27 @@ namespace Kyoo.Authentication.Views } } + /// + /// Get profile picture + /// + /// + /// Get your profile picture + /// + /// The user is not authenticated. + /// The given access token is invalid. + [HttpGet("me/logo")] + [UserOnly] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(RequestError))] + [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))] + public async Task GetProfilePicture() + { + Stream img = await thumbs.GetUserImage(User.GetIdOrThrow()); + // Allow clients to cache the image for 6 month. + Response.Headers.Add("Cache-Control", $"public, max-age={60 * 60 * 24 * 31 * 6}"); + return File(img, "image/webp", true); + } + /// /// Set profile picture /// @@ -324,24 +345,22 @@ namespace Kyoo.Authentication.Views } /// - /// Get profile picture + /// Delete profile picture /// /// - /// Get your profile picture + /// Delete your profile picture /// /// The user is not authenticated. /// The given access token is invalid. - [HttpGet("me/logo")] + [HttpDelete("me/logo")] [UserOnly] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))] - public async Task GetProfilePicture() + public async Task DeleteProfilePicture() { - Stream img = await thumbs.GetUserImage(User.GetIdOrThrow()); - // Allow clients to cache the image for 6 month. - Response.Headers.Add("Cache-Control", $"public, max-age={60 * 60 * 24 * 31 * 6}"); - return File(img, "image/webp", true); + await thumbs.SetUserImage(User.GetIdOrThrow(), null); + return NoContent(); } } } diff --git a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs index 5e7b076b..e0bd5a95 100644 --- a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -245,6 +245,14 @@ namespace Kyoo.Core.Controllers public async Task SetUserImage(Guid userId, Stream? image) { + if (image == null) + { + try + { + File.Delete($"/metadata/user/{userId}.webp"); + } catch { } + return; + } using SKCodec codec = SKCodec.Create(image); SKImageInfo info = codec.Info; info.ColorType = SKColorType.Rgba8888; diff --git a/back/src/Kyoo.Core/Views/Resources/UserApi.cs b/back/src/Kyoo.Core/Views/Resources/UserApi.cs index 0f5ef825..f9cfa41e 100644 --- a/back/src/Kyoo.Core/Views/Resources/UserApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/UserApi.cs @@ -67,5 +67,51 @@ public class UserApi(ILibraryManager libraryManager, IThumbnailsManager thumbs) } return File(img, "image/webp", true); } + + /// + /// Set profile picture + /// + /// + /// Set user profile picture + /// + [HttpPost("{identifier:id}/logo")] + [PartialPermission(Kind.Write)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(RequestError))] + [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))] + public async Task SetProfilePicture(Identifier identifier, IFormFile picture) + { + if (picture == null || picture.Length == 0) + return BadRequest(); + Guid gid = await identifier.Match( + id => Task.FromResult(id), + async slug => (await libraryManager.Users.Get(slug)).Id + ); + await thumbs.SetUserImage(gid, picture.OpenReadStream()); + return NoContent(); + } + + /// + /// Delete profile picture + /// + /// + /// Delete your profile picture + /// + /// The user is not authenticated. + /// The given access token is invalid. + [HttpDelete("{identifier:id}/logo")] + [PartialPermission(Kind.Delete)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(RequestError))] + [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))] + public async Task DeleteProfilePicture(Identifier identifier) + { + Guid gid = await identifier.Match( + id => Task.FromResult(id), + async slug => (await libraryManager.Users.Get(slug)).Id + ); + await thumbs.SetUserImage(gid, null); + return NoContent(); + } }