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();
+ }
}