Media poster methods added

This commit is contained in:
2024-09-11 15:59:13 +02:00
Unverified
parent 5b871714fa
commit cc22e609e1
15 changed files with 250 additions and 24 deletions

View File

@@ -24,6 +24,7 @@ public class MediaPhotoRequest : MediaPhoto
item.MediaId = MediaId;
item.Image = Image;
item.MimeType = MimeType;
item.UploadDate = DateTime.Now;
}
public void UpdateMediaPhotoImageBackground(MediaPhotoImageBackground item)

View File

@@ -2,7 +2,7 @@
namespace WatchIt.Common.Model.Media;
public class MediaPosterImage
public abstract class MediaPoster
{
[JsonPropertyName("image")]
public required byte[] Image { get; set; }

View File

@@ -0,0 +1,19 @@
using WatchIt.Database.Model.Media;
namespace WatchIt.Common.Model.Media;
public class MediaPosterRequest : MediaPoster
{
public MediaPosterImage CreateMediaPosterImage() => new MediaPosterImage
{
Image = Image,
MimeType = MimeType,
};
public void UpdateMediaPosterImage(MediaPosterImage item)
{
item.Image = Image;
item.MimeType = MimeType;
item.UploadDate = DateTime.Now;
}
}

View File

@@ -0,0 +1,36 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using WatchIt.Database.Model.Media;
namespace WatchIt.Common.Model.Media;
public class MediaPosterResponse : MediaPoster
{
#region PROPERTIES
[JsonPropertyName("id")]
public Guid Id { get; set; }
[JsonPropertyName("upload_date")]
public DateTime UploadDate { get; set; }
#endregion
#region CONSTRUCTORS
[JsonConstructor]
public MediaPosterResponse() {}
[SetsRequiredMembers]
public MediaPosterResponse(MediaPosterImage mediaPhotoImage)
{
Id = mediaPhotoImage.Id;
Image = mediaPhotoImage.Image;
MimeType = mediaPhotoImage.MimeType;
UploadDate = mediaPhotoImage.UploadDate;
}
#endregion
}

View File

@@ -7,7 +7,7 @@ public class HttpResponse
{
#region FIELDS
private HttpResponseMessage _message;
private readonly HttpResponseMessage _message;
private Action? _2XXAction;
private Action? _400Action;
@@ -32,37 +32,37 @@ public class HttpResponse
#region PUBLIC METHODS
public HttpResponse RegisterActionFor2XXSuccess(Action action)
public HttpResponse RegisterActionFor2XXSuccess(Action? action)
{
_2XXAction = action;
return this;
}
public HttpResponse RegisterActionFor2XXSuccess<T>(Action<T> action)
public HttpResponse RegisterActionFor2XXSuccess<T>(Action<T>? action)
{
_2XXAction = () => Invoke(action);
return this;
}
public HttpResponse RegisterActionFor400BadRequest(Action<IDictionary<string, string[]>> action)
public HttpResponse RegisterActionFor400BadRequest(Action<IDictionary<string, string[]>>? action)
{
_400Action = () => Invoke(action);
return this;
}
public HttpResponse RegisterActionFor401Unauthorized(Action action)
public HttpResponse RegisterActionFor401Unauthorized(Action? action)
{
_401Action = action;
return this;
}
public HttpResponse RegisterActionFor403Forbidden(Action action)
public HttpResponse RegisterActionFor403Forbidden(Action? action)
{
_403Action = action;
return this;
}
public HttpResponse RegisterActionFor404NotFound(Action action)
public HttpResponse RegisterActionFor404NotFound(Action? action)
{
_404Action = action;
return this;
@@ -86,21 +86,21 @@ public class HttpResponse
#region PRIVATE METHODS
private async void Invoke<T>(Action<T> action)
private async void Invoke<T>(Action<T>? action)
{
Stream streamData = await _message.Content.ReadAsStreamAsync();
T? data = await JsonSerializer.DeserializeAsync<T>(streamData);
action.Invoke(data!);
action?.Invoke(data!);
}
private async void Invoke(Action<IDictionary<string, string[]>> action)
private async void Invoke(Action<IDictionary<string, string[]>>? action)
{
Stream streamData = await _message.Content.ReadAsStreamAsync();
ValidationProblemDetails? data = await JsonSerializer.DeserializeAsync<ValidationProblemDetails>(streamData, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
});
action.Invoke(data!.Errors);
action?.Invoke(data!.Errors);
}
#endregion

View File

@@ -7,7 +7,7 @@ public class MediaPosterImage
public Guid Id { get; set; }
public required byte[] Image { get; set; }
public required string MimeType { get; set; }
public required DateTime UploadDate { get; set; }
public DateTime UploadDate { get; set; }
#endregion

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Model.Genres;
@@ -18,7 +19,7 @@ public class MediaController(IMediaControllerService mediaControllerService)
public async Task<ActionResult> GetGenres([FromRoute]long id) => await mediaControllerService.GetGenres(id);
[HttpPost("{id}/genres/{genre_id}")]
[Authorize]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
@@ -26,7 +27,7 @@ public class MediaController(IMediaControllerService mediaControllerService)
public async Task<ActionResult> PostGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.PostGenre(id, genreId);
[HttpDelete("{id}/genres/{genre_id}")]
[Authorize]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
@@ -39,6 +40,28 @@ public class MediaController(IMediaControllerService mediaControllerService)
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetMediaRandomBackgroundPhoto([FromRoute]long id) => await mediaControllerService.GetMediaRandomBackgroundPhoto(id);
[HttpGet("{id}/poster")]
[AllowAnonymous]
[ProducesResponseType(typeof(MediaPosterResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetPoster([FromRoute] long id) => await mediaControllerService.GetPoster(id);
[HttpPut("{id}/poster")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
public async Task<ActionResult> PutPoster([FromRoute]long id, [FromBody]MediaPosterRequest body) => await mediaControllerService.PutPoster(id, body);
[HttpDelete("{id}/poster")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
public async Task<ActionResult> DeletePoster([FromRoute]long id) => await mediaControllerService.DeletePoster(id);
[HttpGet("photos/{photo_id}")]
[AllowAnonymous]
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)]
@@ -57,7 +80,7 @@ public class MediaController(IMediaControllerService mediaControllerService)
public async Task<ActionResult> GetRandomBackgroundPhoto() => await mediaControllerService.GetRandomBackgroundPhoto();
[HttpPost("photos")]
[Authorize]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
@@ -65,7 +88,7 @@ public class MediaController(IMediaControllerService mediaControllerService)
public async Task<ActionResult> PostPhoto([FromBody]MediaPhotoRequest body) => await mediaControllerService.PostPhoto(body);
[HttpPut("photos/{photo_id}")]
[Authorize]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
@@ -74,7 +97,7 @@ public class MediaController(IMediaControllerService mediaControllerService)
public async Task<ActionResult> PutPhoto([FromRoute(Name = "photo_id")]Guid photoId, [FromBody]MediaPhotoRequest body) => await mediaControllerService.PutPhoto(photoId, body);
[HttpDelete("photos/{photo_id}")]
[Authorize]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]

View File

@@ -12,6 +12,9 @@ public interface IMediaControllerService
Task<RequestResult> GetPhotos(MediaPhotoQueryParameters query);
Task<RequestResult> GetRandomBackgroundPhoto();
Task<RequestResult> GetMediaRandomBackgroundPhoto(long id);
Task<RequestResult> GetPoster(long id);
Task<RequestResult> PutPoster(long id, MediaPosterRequest data);
Task<RequestResult> DeletePoster(long id);
Task<RequestResult> PostPhoto(MediaPhotoRequest data);
Task<RequestResult> PutPhoto(Guid photoId, MediaPhotoRequest data);
Task<RequestResult> DeletePhoto(Guid photoId);

View File

@@ -114,6 +114,76 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
return Task.FromResult<RequestResult>(RequestResult.Ok(data));
}
public async Task<RequestResult> GetPoster(long id)
{
Database.Model.Media.Media? media = await database.Media.FirstOrDefaultAsync(x => x.Id == id);
if (media is null)
{
return RequestResult.BadRequest();
}
MediaPosterImage? poster = media.MediaPosterImage;
if (poster is null)
{
return RequestResult.NotFound();
}
MediaPosterResponse data = new MediaPosterResponse(poster);
return RequestResult.Ok(data);
}
public async Task<RequestResult> PutPoster(long id, MediaPosterRequest data)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();
if (!validator.IsValid)
{
return RequestResult.Forbidden();
}
Database.Model.Media.Media? media = await database.Media.FirstOrDefaultAsync(x => x.Id == id);
if (media is null)
{
return RequestResult.BadRequest();
}
if (media.MediaPosterImage is null)
{
MediaPosterImage image = data.CreateMediaPosterImage();
await database.MediaPosterImages.AddAsync(image);
await database.SaveChangesAsync();
media.MediaPosterImageId = image.Id;
}
else
{
data.UpdateMediaPosterImage(media.MediaPosterImage);
}
await database.SaveChangesAsync();
return RequestResult.Ok();
}
public async Task<RequestResult> DeletePoster(long id)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();
if (!validator.IsValid)
{
return RequestResult.Forbidden();
}
Database.Model.Media.Media? media = await database.Media.FirstOrDefaultAsync(x => x.Id == id);
if (media?.MediaPosterImage != null)
{
database.MediaPosterImages.Attach(media.MediaPosterImage);
database.MediaPosterImages.Remove(media.MediaPosterImage);
await database.SaveChangesAsync();
}
return RequestResult.NoContent();
}
public async Task<RequestResult> PostPhoto(MediaPhotoRequest data)
{
UserValidator validator = userService.GetValidator().MustBeAdmin();

View File

@@ -0,0 +1,14 @@
using FluentValidation;
using WatchIt.Common.Model.Media;
using WatchIt.Database;
namespace WatchIt.WebAPI.Validators.Media;
public class MediaPosterRequestValidator : AbstractValidator<MediaPosterRequest>
{
public MediaPosterRequestValidator()
{
RuleFor(x => x.Image).NotEmpty();
RuleFor(x => x.MimeType).Matches(@"\w+/.+").WithMessage("Incorrect mimetype");
}
}

View File

@@ -4,9 +4,10 @@ using System.Text.Json;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
using WatchIt.Common.Model.Accounts;
using WatchIt.Website.Services.Utility.Tokens;
using WatchIt.Website.Services.WebAPI.Accounts;
namespace WatchIt.Website.Services.Utility.Tokens;
namespace WatchIt.Website.Services.Utility.Authentication;
public class JWTAuthenticationStateProvider : AuthenticationStateProvider
{
@@ -111,13 +112,20 @@ public class JWTAuthenticationStateProvider : AuthenticationStateProvider
private async Task<string?> Refresh(string refreshToken)
{
AuthenticateResponse response = null;
AuthenticateResponse? response = null;
await _accountsService.AuthenticateRefresh((data) => response = data);
await _tokensService.SaveAuthenticationData(response);
if (response is not null)
{
await _tokensService.SaveAuthenticationData(response);
}
else
{
await _tokensService.RemoveAuthenticationData();
}
return response.AccessToken;
return response?.AccessToken;
}
private static IEnumerable<Claim> GetClaimsFromToken(string token)

View File

@@ -7,6 +7,9 @@ public class Media
public string PostGenre { get; set; }
public string DeleteGenre { get; set; }
public string GetPhotoMediaRandomBackground { get; set; }
public string GetPoster { get; set; }
public string PutPoster { get; set; }
public string DeletePoster { get; set; }
public string GetPhoto { get; set; }
public string GetPhotos { get; set; }
public string GetPhotoRandomBackground { get; set; }

View File

@@ -8,4 +8,7 @@ public interface IMediaWebAPIService
Task GetGenres(long mediaId, Action<IEnumerable<GenreResponse>>? successAction = null, Action? notFoundAction = null);
Task PostGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
Task GetPhotoRandomBackground(Action<MediaPhotoResponse>? successAction = null, Action? notFoundAction = null);
Task GetPoster(long mediaId, Action<MediaPosterResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
Task PutPoster(long mediaId, MediaPosterRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
Task DeletePoster(long mediaId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
}

View File

@@ -49,6 +49,49 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
.ExecuteAction();
}
public async Task GetPoster(long mediaId, Action<MediaPosterResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null)
{
string url = GetUrl(EndpointsConfiguration.Media.GetPoster, mediaId);
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor400BadRequest(badRequestAction)
.RegisterActionFor404NotFound(notFoundAction)
.ExecuteAction();
}
public async Task PutPoster(long mediaId, MediaPosterRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
{
string url = GetUrl(EndpointsConfiguration.Media.PutPoster, mediaId);
HttpRequest request = new HttpRequest(HttpMethodType.Get, url)
{
Body = data
};
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor400BadRequest(badRequestAction)
.RegisterActionFor401Unauthorized(unauthorizedAction)
.RegisterActionFor403Forbidden(forbiddenAction)
.ExecuteAction();
}
public async Task DeletePoster(long mediaId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
{
string url = GetUrl(EndpointsConfiguration.Media.DeletePoster, mediaId);
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor401Unauthorized(unauthorizedAction)
.RegisterActionFor403Forbidden(forbiddenAction)
.ExecuteAction();
}
#endregion

View File

@@ -42,6 +42,9 @@
"PostGenre": "/{0}/genres/{1}",
"DeleteGenre": "/{0}/genres/{1}",
"GetPhotoMediaRandomBackground": "/{0}/photos/random_background",
"GetPoster": "/{0}/poster",
"PutPoster": "/{0}/poster",
"DeletePoster": "/{0}/poster",
"GetPhoto": "/photos/{0}",
"GetPhotos": "/photos",
"GetPhotoRandomBackground": "/photos/random_background",