diff --git a/WatchIt.Common/WatchIt.Common.Model/Media/MediaRatingRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Media/MediaRatingRequest.cs new file mode 100644 index 0000000..6358b4a --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Media/MediaRatingRequest.cs @@ -0,0 +1,26 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace WatchIt.Common.Model.Media; + +public class MediaRatingRequest +{ + #region PROPERTIES + + [JsonPropertyName("rating")] + public required short Rating { get; set; } + + #endregion + + + + #region CONSTRUCTORS + + [SetsRequiredMembers] + public MediaRatingRequest(short rating) + { + Rating = rating; + } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Media/MediaRatingResponse.cs b/WatchIt.Common/WatchIt.Common.Model/Media/MediaRatingResponse.cs new file mode 100644 index 0000000..e51c9e9 --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Media/MediaRatingResponse.cs @@ -0,0 +1,33 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace WatchIt.Common.Model.Media; + +public class MediaRatingResponse +{ + #region PROPERTIES + + [JsonPropertyName("rating_average")] + public required double RatingAverage { get; set; } + + [JsonPropertyName("rating_count")] + public required long RatingCount { get; set; } + + #endregion + + + + #region CONSTRUCTORS + + [JsonConstructor] + public MediaRatingResponse() {} + + [SetsRequiredMembers] + public MediaRatingResponse(double ratingAverage, long ratingCount) + { + RatingAverage = ratingAverage; + RatingCount = ratingCount; + } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/MediaController.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/MediaController.cs index f09ca53..75eb1f5 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/MediaController.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/MediaController.cs @@ -12,17 +12,25 @@ namespace WatchIt.WebAPI.Controllers; [Route("media")] public class MediaController(IMediaControllerService mediaControllerService) { + #region MAIN + [HttpGet("{id}")] [AllowAnonymous] [ProducesResponseType(typeof(MediaResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetMedia([FromRoute] long id) => await mediaControllerService.GetMedia(id); + #endregion + + + + #region GENRES + [HttpGet("{id}/genres")] [AllowAnonymous] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetGenres([FromRoute]long id) => await mediaControllerService.GetGenres(id); + public async Task GetMediaGenres([FromRoute]long id) => await mediaControllerService.GetMediaGenres(id); [HttpPost("{id}/genres/{genre_id}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] @@ -30,7 +38,7 @@ public class MediaController(IMediaControllerService mediaControllerService) [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PostGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.PostGenre(id, genreId); + public async Task PostMediaGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.PostMediaGenre(id, genreId); [HttpDelete("{id}/genres/{genre_id}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] @@ -38,20 +46,52 @@ public class MediaController(IMediaControllerService mediaControllerService) [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.DeleteGenre(id, genreId); + public async Task DeleteMediaGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.DeleteMediaGenre(id, genreId); - [HttpGet("{id}/photos/random_background")] - [AllowAnonymous] - [ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetMediaRandomBackgroundPhoto([FromRoute]long id) => await mediaControllerService.GetMediaRandomBackgroundPhoto(id); + #endregion + + + #region RATING + + [HttpGet("{id}/rating")] + [AllowAnonymous] + [ProducesResponseType(typeof(MediaRatingResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetMediaRating([FromRoute] long id) => await mediaControllerService.GetMediaRating(id); + + [HttpGet("{id}/rating/{user_id}")] + [AllowAnonymous] + [ProducesResponseType(typeof(short), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetMediaRatingByUser([FromRoute] long id, [FromRoute(Name = "user_id")]long userId) => await mediaControllerService.GetMediaRatingByUser(id, userId); + + [HttpPut("{id}/rating")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PutMediaRating([FromRoute] long id, [FromBody] MediaRatingRequest data) => await mediaControllerService.PutMediaRating(id, data); + + [HttpDelete("{id}/rating")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] + public async Task DeleteMediaRating([FromRoute] long id) => await mediaControllerService.DeleteMediaRating(id); + + #endregion + + + + #region POSTER + [HttpGet("{id}/poster")] [AllowAnonymous] [ProducesResponseType(typeof(MediaPosterResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetPoster([FromRoute] long id) => await mediaControllerService.GetPoster(id); + public async Task GetMediaPoster([FromRoute] long id) => await mediaControllerService.GetMediaPoster(id); [HttpPut("{id}/poster")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] @@ -59,14 +99,26 @@ public class MediaController(IMediaControllerService mediaControllerService) [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)] - public async Task PutPoster([FromRoute]long id, [FromBody]MediaPosterRequest body) => await mediaControllerService.PutPoster(id, body); + public async Task PutMediaPoster([FromRoute]long id, [FromBody]MediaPosterRequest body) => await mediaControllerService.PutMediaPoster(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 DeletePoster([FromRoute]long id) => await mediaControllerService.DeletePoster(id); + public async Task DeleteMediaPoster([FromRoute]long id) => await mediaControllerService.DeleteMediaPoster(id); + + #endregion + + + + #region PHOTOS + + [HttpGet("{id}/photos/random_background")] + [AllowAnonymous] + [ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetMediaPhotoRandomBackground([FromRoute]long id) => await mediaControllerService.GetMediaRandomBackgroundPhoto(id); [HttpGet("photos/{photo_id}")] [AllowAnonymous] @@ -83,7 +135,7 @@ public class MediaController(IMediaControllerService mediaControllerService) [AllowAnonymous] [ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetRandomBackgroundPhoto() => await mediaControllerService.GetRandomBackgroundPhoto(); + public async Task GetPhotoRandomBackground() => await mediaControllerService.GetRandomBackgroundPhoto(); [HttpPost("photos")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] @@ -109,4 +161,6 @@ public class MediaController(IMediaControllerService mediaControllerService) [ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeletePhoto([FromRoute(Name = "photo_id")]Guid photoId) => await mediaControllerService.DeletePhoto(photoId); + + #endregion } \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/IMediaControllerService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/IMediaControllerService.cs index ab543a0..f700a4a 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/IMediaControllerService.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/IMediaControllerService.cs @@ -6,16 +6,24 @@ namespace WatchIt.WebAPI.Services.Controllers.Media; public interface IMediaControllerService { Task GetMedia(long mediaId); - Task GetGenres(long mediaId); - Task PostGenre(long mediaId, short genreId); - Task DeleteGenre(long mediaId, short genreId); + + Task GetMediaGenres(long mediaId); + Task PostMediaGenre(long mediaId, short genreId); + Task DeleteMediaGenre(long mediaId, short genreId); + + Task GetMediaRating(long mediaId); + Task GetMediaRatingByUser(long mediaId, long userId); + Task PutMediaRating(long mediaId, MediaRatingRequest data); + Task DeleteMediaRating(long mediaId); + + Task GetMediaPoster(long id); + Task PutMediaPoster(long id, MediaPosterRequest data); + Task DeleteMediaPoster(long id); + Task GetPhoto(Guid id); Task GetPhotos(MediaPhotoQueryParameters query); Task GetRandomBackgroundPhoto(); Task GetMediaRandomBackgroundPhoto(long id); - Task GetPoster(long id); - Task PutPoster(long id, MediaPosterRequest data); - Task DeletePoster(long id); Task PostPhoto(MediaPhotoRequest data); Task PutPhoto(Guid photoId, MediaPhotoRequest data); Task DeletePhoto(Guid photoId); diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/MediaControllerService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/MediaControllerService.cs index daedf0c..1dd91c8 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/MediaControllerService.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/MediaControllerService.cs @@ -4,6 +4,7 @@ using WatchIt.Common.Model.Genres; using WatchIt.Common.Model.Media; using WatchIt.Database; using WatchIt.Database.Model.Media; +using WatchIt.Database.Model.Rating; using WatchIt.WebAPI.Services.Controllers.Common; using WatchIt.WebAPI.Services.Utility.User; @@ -13,6 +14,8 @@ public class MediaControllerService(DatabaseContext database, IUserService userS { #region PUBLIC METHODS + #region Main + public async Task GetMedia(long mediaId) { Database.Model.Media.Media? item = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId); @@ -27,8 +30,12 @@ public class MediaControllerService(DatabaseContext database, IUserService userS return RequestResult.Ok(mediaResponse); } - - public async Task GetGenres(long mediaId) + + #endregion + + #region Genres + + public async Task GetMediaGenres(long mediaId) { Database.Model.Media.Media? item = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId); if (item is null) @@ -40,7 +47,7 @@ public class MediaControllerService(DatabaseContext database, IUserService userS return RequestResult.Ok(genres); } - public async Task PostGenre(long mediaId, short genreId) + public async Task PostMediaGenre(long mediaId, short genreId) { UserValidator validator = userService.GetValidator().MustBeAdmin(); if (!validator.IsValid) @@ -65,7 +72,7 @@ public class MediaControllerService(DatabaseContext database, IUserService userS return RequestResult.Ok(); } - public async Task DeleteGenre(long mediaId, short genreId) + public async Task DeleteMediaGenre(long mediaId, short genreId) { UserValidator validator = userService.GetValidator().MustBeAdmin(); if (!validator.IsValid) @@ -86,6 +93,173 @@ public class MediaControllerService(DatabaseContext database, IUserService userS return RequestResult.Ok(); } + #endregion + + #region Rating + + public async Task GetMediaRating(long mediaId) + { + Database.Model.Media.Media? item = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId); + if (item is null) + { + return RequestResult.NotFound(); + } + + double ratingAverage = item.RatingMedia.Any() ? item.RatingMedia.Average(x => x.Rating) : 0; + long ratingCount = item.RatingMedia.Count(); + MediaRatingResponse ratingResponse = new MediaRatingResponse(ratingAverage, ratingCount); + + return RequestResult.Ok(ratingResponse); + } + + public async Task GetMediaRatingByUser(long mediaId, long userId) + { + Database.Model.Media.Media? item = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId); + if (item is null) + { + return RequestResult.NotFound(); + } + + short? rating = item.RatingMedia.FirstOrDefault(x => x.AccountId == userId)?.Rating; + if (!rating.HasValue) + { + return RequestResult.NotFound(); + } + + return RequestResult.Ok(rating.Value); + } + + public async Task PutMediaRating(long mediaId, MediaRatingRequest data) + { + short ratingValue = data.Rating; + if (ratingValue < 1 || ratingValue > 10) + { + return RequestResult.BadRequest(); + } + + Database.Model.Media.Media? item = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId); + if (item is null) + { + return RequestResult.NotFound(); + } + + long userId = userService.GetUserId(); + + RatingMedia? rating = item.RatingMedia.FirstOrDefault(x => x.AccountId == userId); + if (rating is not null) + { + rating.Rating = ratingValue; + } + else + { + rating = new RatingMedia + { + AccountId = userId, + MediaId = mediaId, + Rating = ratingValue + }; + await database.RatingsMedia.AddAsync(rating); + } + await database.SaveChangesAsync(); + + return RequestResult.Ok(); + } + + public async Task DeleteMediaRating(long mediaId) + { + long userId = userService.GetUserId(); + + RatingMedia? item = await database.RatingsMedia.FirstOrDefaultAsync(x => x.MediaId == mediaId && x.AccountId == userId); + if (item is null) + { + return RequestResult.Ok(); + } + + database.RatingsMedia.Attach(item); + database.RatingsMedia.Remove(item); + await database.SaveChangesAsync(); + + return RequestResult.Ok(); + } + + #endregion + + #region Poster + + public async Task GetMediaPoster(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 PutMediaPoster(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 DeleteMediaPoster(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(); + } + + #endregion + + #region Photos + public async Task GetPhoto(Guid id) { MediaPhotoImage? item = await database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == id); @@ -129,76 +303,6 @@ public class MediaControllerService(DatabaseContext database, IUserService userS return Task.FromResult(RequestResult.Ok(data)); } - public async Task 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 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 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 PostPhoto(MediaPhotoRequest data) { UserValidator validator = userService.GetValidator().MustBeAdmin(); @@ -284,4 +388,6 @@ public class MediaControllerService(DatabaseContext database, IUserService userS } #endregion + + #endregion } \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/IUserService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/IUserService.cs index d86d330..1b32d96 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/IUserService.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/IUserService.cs @@ -8,4 +8,5 @@ public interface IUserService string? GetRawToken(); UserValidator GetValidator(); Guid GetJti(); + long GetUserId(); } \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/UserService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/UserService.cs index b6e5bcd..58c067d 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/UserService.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/UserService.cs @@ -40,6 +40,14 @@ public class UserService(DatabaseContext database, IHttpContextAccessor accessor Guid guid = Guid.Parse(jtiClaim.Value); return guid; } + + public long GetUserId() + { + ClaimsPrincipal user = GetRawUser(); + Claim subClaim = user.FindFirst(JwtRegisteredClaimNames.Sub)!; + long id = long.Parse(subClaim.Value); + return id; + } #endregion } \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs b/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs index 212d435..215523c 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs @@ -1,3 +1,4 @@ +using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Text; using FluentValidation; @@ -5,6 +6,7 @@ using FluentValidation.AspNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using WatchIt.Database; using WatchIt.WebAPI.Services.Controllers.Accounts; @@ -26,12 +28,12 @@ public static class Program public static void Main(string[] args) { WebApplication app = WebApplication.CreateBuilder(args) - .SetupAuthentication() - .SetupDatabase() - .SetupWorkerServices() - .SetupServices() - .SetupApplication() - .Build(); + .SetupAuthentication() + .SetupDatabase() + .SetupWorkerServices() + .SetupServices() + .SetupApplication() + .Build(); if (app.Environment.IsDevelopment()) { @@ -40,7 +42,8 @@ public static class Program } app.UseHttpsRedirection(); - + + app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); @@ -56,6 +59,9 @@ public static class Program private static WebApplicationBuilder SetupAuthentication(this WebApplicationBuilder builder) { + JsonWebTokenHandler.DefaultInboundClaimTypeMap.Clear(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + AuthenticationBuilder authenticationBuilder = builder.Services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Media.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Media.cs index bbe65e0..c2ff6ab 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Media.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Media.cs @@ -7,6 +7,10 @@ public class Media public string GetGenres { get; set; } public string PostGenre { get; set; } public string DeleteGenre { get; set; } + public string GetMediaRating { get; set; } + public string GetMediaRatingByUser { get; set; } + public string PutMediaRating { get; set; } + public string DeleteMediaRating { get; set; } public string GetPhotoMediaRandomBackground { get; set; } public string GetPoster { get; set; } public string PutPoster { get; set; } diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Media/IMediaWebAPIService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Media/IMediaWebAPIService.cs index 6c4ab8f..8868cae 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Media/IMediaWebAPIService.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Media/IMediaWebAPIService.cs @@ -5,9 +5,16 @@ namespace WatchIt.Website.Services.WebAPI.Media; public interface IMediaWebAPIService { - Task Get(long mediaId, Action successAction = null, Action? notFoundAction = null); - Task GetGenres(long mediaId, Action>? successAction = null, Action? notFoundAction = null); - Task PostGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null); + Task GetMedia(long mediaId, Action? successAction = null, Action? notFoundAction = null); + + Task GetMediaGenres(long mediaId, Action>? successAction = null, Action? notFoundAction = null); + Task PostMediaGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null); + + Task GetMediaRating(long mediaId, Action? successAction = null, Action? notFoundAction = null); + Task GetMediaRatingByUser(long mediaId, long userId, Action? successAction = null, Action? notFoundAction = null); + Task PutMediaRating(long mediaId, MediaRatingRequest body, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null); + Task DeleteMediaRating(long mediaId, Action? successAction = null, Action? unauthorizedAction = null); + Task GetPhotoMediaRandomBackground(long mediaId, Action? successAction = null, Action? notFoundAction = null); Task GetPhotoRandomBackground(Action? successAction = null, Action? notFoundAction = null); Task GetPoster(long mediaId, Action? successAction = null, Action>? badRequestAction = null, Action? notFoundAction = null); diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Media/MediaWebAPIService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Media/MediaWebAPIService.cs index 99d73cb..16b00df 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Media/MediaWebAPIService.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Media/MediaWebAPIService.cs @@ -10,8 +10,10 @@ namespace WatchIt.Website.Services.WebAPI.Media; public class MediaWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : BaseWebAPIService(configurationService), IMediaWebAPIService { #region PUBLIC METHODS - - public async Task Get(long mediaId, Action successAction = null, Action? notFoundAction = null) + + #region Main + + public async Task GetMedia(long mediaId, Action? successAction = null, Action? notFoundAction = null) { string url = GetUrl(EndpointsConfiguration.Media.Get, mediaId); @@ -23,7 +25,11 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura .ExecuteAction(); } - public async Task GetGenres(long mediaId, Action>? successAction = null, Action? notFoundAction = null) + #endregion + + #region Genres + + public async Task GetMediaGenres(long mediaId, Action>? successAction = null, Action? notFoundAction = null) { string url = GetUrl(EndpointsConfiguration.Media.GetGenres, mediaId); @@ -35,7 +41,7 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura .ExecuteAction(); } - public async Task PostGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null) + public async Task PostMediaGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null) { string url = GetUrl(EndpointsConfiguration.Media.PostGenre, mediaId, genreId); @@ -49,6 +55,67 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura .ExecuteAction(); } + #endregion + + #region Rating + + public async Task GetMediaRating(long mediaId, Action? successAction = null, Action? notFoundAction = null) + { + string url = GetUrl(EndpointsConfiguration.Media.GetMediaRating, mediaId); + + HttpRequest request = new HttpRequest(HttpMethodType.Get, url); + + HttpResponse response = await httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor404NotFound(notFoundAction) + .ExecuteAction(); + } + + public async Task GetMediaRatingByUser(long mediaId, long userId, Action? successAction = null, Action? notFoundAction = null) + { + string url = GetUrl(EndpointsConfiguration.Media.GetMediaRatingByUser, mediaId, userId); + + HttpRequest request = new HttpRequest(HttpMethodType.Get, url); + + HttpResponse response = await httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor404NotFound(notFoundAction) + .ExecuteAction(); + } + + public async Task PutMediaRating(long mediaId, MediaRatingRequest body, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null) + { + string url = GetUrl(EndpointsConfiguration.Media.PutMediaRating, mediaId); + + HttpRequest request = new HttpRequest(HttpMethodType.Put, url) + { + Body = body + }; + + HttpResponse response = await httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor400BadRequest(badRequestAction) + .RegisterActionFor401Unauthorized(unauthorizedAction) + .RegisterActionFor404NotFound(notFoundAction) + .ExecuteAction(); + } + + public async Task DeleteMediaRating(long mediaId, Action? successAction = null, Action? unauthorizedAction = null) + { + string url = GetUrl(EndpointsConfiguration.Media.DeleteMediaRating, mediaId); + + HttpRequest request = new HttpRequest(HttpMethodType.Delete, url); + + HttpResponse response = await httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor401Unauthorized(unauthorizedAction) + .ExecuteAction(); + } + + #endregion + + + public async Task GetPhotoMediaRandomBackground(long mediaId, Action? successAction = null, Action? notFoundAction = null) { string url = GetUrl(EndpointsConfiguration.Media.GetPhotoMediaRandomBackground, mediaId); diff --git a/WatchIt.Website/WatchIt.Website/App.razor b/WatchIt.Website/WatchIt.Website/App.razor index 2bbaf6c..aa8ad3b 100644 --- a/WatchIt.Website/WatchIt.Website/App.razor +++ b/WatchIt.Website/WatchIt.Website/App.razor @@ -10,7 +10,7 @@ - + diff --git a/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor b/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor index 03c88ff..ff057ee 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor +++ b/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor @@ -1,10 +1,28 @@ -@page "/media/{id:long}" -@using System.Text +@using System.Text @using Microsoft.IdentityModel.Tokens @using WatchIt.Common.Model.Genres +@page "/media/{id:long}" + @layout MainLayout +@if (_loaded) +{ + if (string.IsNullOrWhiteSpace(_error)) + { + @_media.Title@(_media.ReleaseDate is not null ? $" ({_media.ReleaseDate.Value.Year})" : string.Empty) - WatchIt + } + else + { + Error - WatchIt + } +} +else +{ + Loading... - WatchIt +} + +
@if (_loaded) @@ -105,8 +123,61 @@
-
- Oceny tutaj będą +
+
+
+
+

+ Global rating: @(_globalRating.RatingCount == 0 ? "no ratings" : $"{Math.Round(_globalRating.RatingAverage, 1)}/10") +

+
+
+
+
+
+
+
+
+
+

+ Your rating: +

+
+
+
+
+ @if (_user is not null) + { +
+ + + + + + + + + + + + + + + + + + + + +
+ } + else + { +

You must be logged in to add a rating

+ } +
+
+
diff --git a/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor.cs b/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor.cs index c204e4e..494706a 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Components; +using System.Diagnostics; +using Microsoft.AspNetCore.Components; using WatchIt.Common.Model.Genres; using WatchIt.Common.Model.Media; using WatchIt.Common.Model.Movies; @@ -38,9 +39,12 @@ public partial class MediaDataPage : ComponentBase private MediaResponse? _media; private MovieResponse? _movie; private IEnumerable _genres; + private MediaRatingResponse _globalRating; private MediaPhotoResponse? _background; private MediaPosterResponse? _poster; private User? _user; + + private short? _userRating; #endregion @@ -52,14 +56,15 @@ public partial class MediaDataPage : ComponentBase { if (firstRender) { - await MediaWebAPIService.Get(Id, data => _media = data, () => _error = $"Media with id {Id} was not found"); + await MediaWebAPIService.GetMedia(Id, data => _media = data, () => _error = $"Media with id {Id} was not found"); if (_error is null) { Task backgroundTask = MediaWebAPIService.GetPhotoMediaRandomBackground(Id, data => _background = data); Task posterTask = MediaWebAPIService.GetPoster(Id, data => _poster = data); Task userTask = AuthenticationService.GetUserAsync(); - Task genresTask = MediaWebAPIService.GetGenres(Id, data => _genres = data); + Task genresTask = MediaWebAPIService.GetMediaGenres(Id, data => _genres = data); + Task globalRatingTask = MediaWebAPIService.GetMediaRating(Id, data => _globalRating = data); Task specificMediaTask; if (_media.Type == MediaType.Movie) { @@ -76,17 +81,43 @@ public partial class MediaDataPage : ComponentBase userTask, specificMediaTask, genresTask, + globalRatingTask, backgroundTask, posterTask, ]); _user = await userTask; } + + if (_user is not null) + { + Task userRatingTask = MediaWebAPIService.GetMediaRatingByUser(Id, _user.Id, data => _userRating = data); + + await Task.WhenAll( + [ + userRatingTask, + ]); + } _loaded = true; StateHasChanged(); } } + private async Task AddRating(short rating) + { + if (_userRating == rating) + { + await MediaWebAPIService.DeleteMediaRating(Id); + _userRating = null; + } + else + { + await MediaWebAPIService.PutMediaRating(Id, new MediaRatingRequest(rating)); + _userRating = rating; + } + await MediaWebAPIService.GetMediaRating(Id, data => _globalRating = data); + } + #endregion } \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor.css b/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor.css index 9ae511b..de598ce 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor.css +++ b/WatchIt.Website/WatchIt.Website/Pages/MediaDataPage.razor.css @@ -15,4 +15,24 @@ .description-shadow { text-shadow: 1px 1px 1px #000; +} + +.rating-separator { + border-color: black; + border-width: 1px; + + margin: 10px 0; +} + +.rating > input { + display: none; +} + +.rating > label { + font-size: 30px; +} + +.rating > label:hover, .rating > input:hover+label { + color: gray !important; + cursor: pointer; } \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/appsettings.json b/WatchIt.Website/WatchIt.Website/appsettings.json index 580f26a..c93fcd9 100644 --- a/WatchIt.Website/WatchIt.Website/appsettings.json +++ b/WatchIt.Website/WatchIt.Website/appsettings.json @@ -42,6 +42,11 @@ "GetGenres": "/{0}/genres", "PostGenre": "/{0}/genres/{1}", "DeleteGenre": "/{0}/genres/{1}", + "GetMediaRating": "/{0}/rating", + "GetMediaRatingByUser": "/{0}/rating/{1}", + "PutMediaRating": "/{0}/rating", + "DeleteMediaRating": "/{0}/rating", + "GetPhotoMediaRandomBackground": "/{0}/photos/random_background", "GetPoster": "/{0}/poster", "PutPoster": "/{0}/poster",