search page finished

This commit is contained in:
2024-09-28 02:36:53 +02:00
Unverified
parent 0994edb983
commit 64c73b7b5d
16 changed files with 212 additions and 25 deletions

View File

@@ -1,9 +1,9 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace WatchIt.Common.Model.Media; namespace WatchIt.Common.Model.Rating;
public class MediaRatingRequest public class RatingRequest
{ {
#region PROPERTIES #region PROPERTIES
@@ -17,7 +17,7 @@ public class MediaRatingRequest
#region CONSTRUCTORS #region CONSTRUCTORS
[SetsRequiredMembers] [SetsRequiredMembers]
public MediaRatingRequest(short rating) public RatingRequest(short rating)
{ {
Rating = rating; Rating = rating;
} }

View File

@@ -1,9 +1,9 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace WatchIt.Common.Model.Media; namespace WatchIt.Common.Model.Rating;
public class MediaRatingResponse public class RatingResponse
{ {
#region PROPERTIES #region PROPERTIES
@@ -20,10 +20,10 @@ public class MediaRatingResponse
#region CONSTRUCTORS #region CONSTRUCTORS
[JsonConstructor] [JsonConstructor]
public MediaRatingResponse() {} public RatingResponse() {}
[SetsRequiredMembers] [SetsRequiredMembers]
public MediaRatingResponse(double ratingAverage, long ratingCount) public RatingResponse(double ratingAverage, long ratingCount)
{ {
RatingAverage = ratingAverage; RatingAverage = ratingAverage;
RatingCount = ratingCount; RatingCount = ratingCount;

View File

@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Model.Genres; using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Media; using WatchIt.Common.Model.Media;
using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Photos;
using WatchIt.Common.Model.Rating;
using WatchIt.WebAPI.Services.Controllers.Media; using WatchIt.WebAPI.Services.Controllers.Media;
namespace WatchIt.WebAPI.Controllers; namespace WatchIt.WebAPI.Controllers;
@@ -74,7 +75,7 @@ public class MediaController : ControllerBase
[HttpGet("{id}/rating")] [HttpGet("{id}/rating")]
[AllowAnonymous] [AllowAnonymous]
[ProducesResponseType(typeof(MediaRatingResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(RatingResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetMediaRating([FromRoute] long id) => await _mediaControllerService.GetMediaRating(id); public async Task<ActionResult> GetMediaRating([FromRoute] long id) => await _mediaControllerService.GetMediaRating(id);
@@ -90,7 +91,7 @@ public class MediaController : ControllerBase
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> PutMediaRating([FromRoute] long id, [FromBody] MediaRatingRequest data) => await _mediaControllerService.PutMediaRating(id, data); public async Task<ActionResult> PutMediaRating([FromRoute] long id, [FromBody] RatingRequest data) => await _mediaControllerService.PutMediaRating(id, data);
[HttpDelete("{id}/rating")] [HttpDelete("{id}/rating")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

View File

@@ -1,5 +1,6 @@
using WatchIt.Common.Model.Media; using WatchIt.Common.Model.Media;
using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Photos;
using WatchIt.Common.Model.Rating;
using WatchIt.WebAPI.Services.Controllers.Common; using WatchIt.WebAPI.Services.Controllers.Common;
namespace WatchIt.WebAPI.Services.Controllers.Media; namespace WatchIt.WebAPI.Services.Controllers.Media;
@@ -14,7 +15,7 @@ public interface IMediaControllerService
Task<RequestResult> GetMediaRating(long mediaId); Task<RequestResult> GetMediaRating(long mediaId);
Task<RequestResult> GetMediaRatingByUser(long mediaId, long userId); Task<RequestResult> GetMediaRatingByUser(long mediaId, long userId);
Task<RequestResult> PutMediaRating(long mediaId, MediaRatingRequest data); Task<RequestResult> PutMediaRating(long mediaId, RatingRequest data);
Task<RequestResult> DeleteMediaRating(long mediaId); Task<RequestResult> DeleteMediaRating(long mediaId);
Task<RequestResult> PostMediaView(long mediaId); Task<RequestResult> PostMediaView(long mediaId);

View File

@@ -3,6 +3,7 @@ using SimpleToolkit.Extensions;
using WatchIt.Common.Model.Genres; using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Media; using WatchIt.Common.Model.Media;
using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Photos;
using WatchIt.Common.Model.Rating;
using WatchIt.Database; using WatchIt.Database;
using WatchIt.Database.Model.Media; using WatchIt.Database.Model.Media;
using WatchIt.Database.Model.Rating; using WatchIt.Database.Model.Rating;
@@ -109,7 +110,7 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
double ratingAverage = item.RatingMedia.Any() ? item.RatingMedia.Average(x => x.Rating) : 0; double ratingAverage = item.RatingMedia.Any() ? item.RatingMedia.Average(x => x.Rating) : 0;
long ratingCount = item.RatingMedia.Count(); long ratingCount = item.RatingMedia.Count();
MediaRatingResponse ratingResponse = new MediaRatingResponse(ratingAverage, ratingCount); RatingResponse ratingResponse = new RatingResponse(ratingAverage, ratingCount);
return RequestResult.Ok(ratingResponse); return RequestResult.Ok(ratingResponse);
} }
@@ -131,7 +132,7 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
return RequestResult.Ok(rating.Value); return RequestResult.Ok(rating.Value);
} }
public async Task<RequestResult> PutMediaRating(long mediaId, MediaRatingRequest data) public async Task<RequestResult> PutMediaRating(long mediaId, RatingRequest data)
{ {
short ratingValue = data.Rating; short ratingValue = data.Rating;
if (ratingValue < 1 || ratingValue > 10) if (ratingValue < 1 || ratingValue > 10)

View File

@@ -1,6 +1,7 @@
using WatchIt.Common.Model.Genres; using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Media; using WatchIt.Common.Model.Media;
using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Photos;
using WatchIt.Common.Model.Rating;
namespace WatchIt.Website.Services.WebAPI.Media; namespace WatchIt.Website.Services.WebAPI.Media;
@@ -12,9 +13,9 @@ public interface IMediaWebAPIService
Task PostMediaGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null); Task PostMediaGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
Task DeleteMediaGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null); Task DeleteMediaGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
Task GetMediaRating(long mediaId, Action<MediaRatingResponse>? successAction = null, Action? notFoundAction = null); Task GetMediaRating(long mediaId, Action<RatingResponse>? successAction = null, Action? notFoundAction = null);
Task GetMediaRatingByUser(long mediaId, long userId, Action<short>? successAction = null, Action? notFoundAction = null); Task GetMediaRatingByUser(long mediaId, long userId, Action<short>? successAction = null, Action? notFoundAction = null);
Task PutMediaRating(long mediaId, MediaRatingRequest body, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null); Task PutMediaRating(long mediaId, RatingRequest body, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null);
Task DeleteMediaRating(long mediaId, Action? successAction = null, Action? unauthorizedAction = null); Task DeleteMediaRating(long mediaId, Action? successAction = null, Action? unauthorizedAction = null);
Task PostMediaView(long mediaId, Action? successAction = null, Action? notFoundAction = null); Task PostMediaView(long mediaId, Action? successAction = null, Action? notFoundAction = null);

View File

@@ -1,6 +1,7 @@
using WatchIt.Common.Model.Genres; using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Media; using WatchIt.Common.Model.Media;
using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Photos;
using WatchIt.Common.Model.Rating;
using WatchIt.Common.Services.HttpClient; using WatchIt.Common.Services.HttpClient;
using WatchIt.Website.Services.Utility.Configuration; using WatchIt.Website.Services.Utility.Configuration;
using WatchIt.Website.Services.Utility.Configuration.Model; using WatchIt.Website.Services.Utility.Configuration.Model;
@@ -93,7 +94,7 @@ public class MediaWebAPIService : BaseWebAPIService, IMediaWebAPIService
#region Rating #region Rating
public async Task GetMediaRating(long mediaId, Action<MediaRatingResponse>? successAction = null, Action? notFoundAction = null) public async Task GetMediaRating(long mediaId, Action<RatingResponse>? successAction = null, Action? notFoundAction = null)
{ {
string url = GetUrl(EndpointsConfiguration.Media.GetMediaRating, mediaId); string url = GetUrl(EndpointsConfiguration.Media.GetMediaRating, mediaId);
@@ -117,7 +118,7 @@ public class MediaWebAPIService : BaseWebAPIService, IMediaWebAPIService
.ExecuteAction(); .ExecuteAction();
} }
public async Task PutMediaRating(long mediaId, MediaRatingRequest body, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null) public async Task PutMediaRating(long mediaId, RatingRequest body, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null)
{ {
string url = GetUrl(EndpointsConfiguration.Media.PutMediaRating, mediaId); string url = GetUrl(EndpointsConfiguration.Media.PutMediaRating, mediaId);

View File

@@ -0,0 +1,26 @@
<div class="container-float m-0 p-0">
<div class="row">
<div class="col-auto">
<img id="picture" class="rounded-2 shadow object-fit-cover picture-aspect-ratio" src="@(_picture is not null ? _picture.ToString() : "assets/poster.png")" alt="picture" height="@(PictureHeight)"/>
</div>
<div class="col">
<div class="d-flex align-items-start flex-column h-100">
<div class="mb-auto">
<span id="nameText">
<strong>@(Name)</strong>@(string.IsNullOrWhiteSpace(AdditionalNameInfo) ? string.Empty : AdditionalNameInfo)
</span>
</div>
<div class="d-inline-flex gap-2">
<span id="ratingStar">★</span>
<div class="d-inline-flex flex-column justify-content-center">
<span id="ratingValue">@(_rating is not null && _rating.RatingCount > 0 ? _rating.RatingAverage : "--")/10</span>
@if (_rating is not null && _rating.RatingCount > 0)
{
<span id="ratingCount">@(_rating.RatingCount)</span>
}
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,56 @@
using Microsoft.AspNetCore.Components;
using WatchIt.Common.Model;
using WatchIt.Common.Model.Rating;
namespace WatchIt.Website.Components;
public partial class ListItemComponent : ComponentBase
{
#region PARAMETERS
[Parameter] public required long Id { get; set; }
[Parameter] public required string Name { get; set; }
[Parameter] public string? AdditionalNameInfo { get; set; }
[Parameter] public required Func<long, Action<Picture>, Task> PictureDownloadingTask { get; set; }
[Parameter] public required Func<long, Action<RatingResponse>, Task> RatingDownloadingTask { get; set; }
[Parameter] public int PictureHeight { get; set; } = 150;
#endregion
#region FIELDS
private bool _loaded;
private Picture? _picture;
private RatingResponse? _rating;
#endregion
#region PRIVATE METHODS
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
List<Task> endTasks = new List<Task>();
// STEP 0
endTasks.AddRange(
[
PictureDownloadingTask(Id, picture => _picture = picture),
RatingDownloadingTask(Id, rating => _rating = rating)
]);
await Task.WhenAll(endTasks);
_loaded = true;
StateHasChanged();
}
}
#endregion
}

View File

@@ -0,0 +1,17 @@
/* IDS */
#nameText {
font-size: 25px;
}
#ratingStar {
font-size: 30px;
}
#ratingValue {
font-size: 20px;
}
#ratingCount {
font-size: 10px;
}

View File

@@ -1,5 +1,6 @@
@typeparam TItem
@using Microsoft.IdentityModel.Tokens @using Microsoft.IdentityModel.Tokens
@typeparam TItem
@typeparam TQuery where TQuery : WatchIt.Common.Query.QueryParameters @typeparam TQuery where TQuery : WatchIt.Common.Query.QueryParameters
@@ -27,7 +28,33 @@
} }
<div class="row"> <div class="row">
<div class="col"> <div class="col">
test <a class="text-reset text-decoration-none" href="@(string.Format(UrlIdTemplate, IdSource(_items[i])))">
<ListItemComponent Id="@(IdSource(_items[i]))"
Name="@(NameSource(_items[i]))"
AdditionalNameInfo="@(AdditionalNameInfoSource(_items[i]))"
PictureDownloadingTask="@(PictureDownloadingTask)"
RatingDownloadingTask="@(RatingDownloadingTask)"/>
</a>
</div>
</div>
}
if (!_allItemsLoaded)
{
<div class="row mt-3">
<div class="col">
<div class="d-flex justify-content-center">
<button class="btn btn-secondary" @onclick="DownloadItems">
@if (!_itemsLoading)
{
<span class="sr-only">Load more</span>
}
else
{
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span class="sr-only">Saving...</span>
}
</button>
</div>
</div> </div>
</div> </div>
} }

View File

@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using WatchIt.Common.Model;
using WatchIt.Common.Model.Rating;
using WatchIt.Common.Query; using WatchIt.Common.Query;
namespace WatchIt.Website.Components.SearchPage; namespace WatchIt.Website.Components.SearchPage;
@@ -9,7 +11,13 @@ public partial class SearchResultComponent<TItem, TQuery> : ComponentBase where
[Parameter] public required string Title { get; set; } [Parameter] public required string Title { get; set; }
[Parameter] public required TQuery Query { get; set; } [Parameter] public required TQuery Query { get; set; }
[Parameter] public Func<TQuery, Action<IEnumerable<TItem>>, Task> DownloadingTask { get; set; } [Parameter] public required Func<TItem, long> IdSource { get; set; }
[Parameter] public required Func<TItem, string> NameSource { get; set; }
[Parameter] public Func<TItem, string?> AdditionalNameInfoSource { get; set; } = _ => null;
[Parameter] public required string UrlIdTemplate { get; set; }
[Parameter] public required Func<TQuery, Action<IEnumerable<TItem>>, Task> ItemDownloadingTask { get; set; }
[Parameter] public required Func<long, Action<Picture>, Task> PictureDownloadingTask { get; set; }
[Parameter] public required Func<long, Action<RatingResponse>, Task> RatingDownloadingTask { get; set; }
#endregion #endregion
@@ -21,7 +29,7 @@ public partial class SearchResultComponent<TItem, TQuery> : ComponentBase where
private List<TItem> _items = []; private List<TItem> _items = [];
private bool _allItemsLoaded; private bool _allItemsLoaded;
private bool _itemsLoading;
#endregion #endregion
@@ -41,7 +49,18 @@ public partial class SearchResultComponent<TItem, TQuery> : ComponentBase where
// STEP 0 // STEP 0
endTasks.AddRange( endTasks.AddRange(
[ [
DownloadingTask(Query, data => _items.AddRange(data)) ItemDownloadingTask(Query, data =>
{
_items.AddRange(data);
if (data.Count() < 5)
{
_allItemsLoaded = true;
}
else
{
Query.After = 5;
}
})
]); ]);
// END // END
@@ -52,5 +71,23 @@ public partial class SearchResultComponent<TItem, TQuery> : ComponentBase where
} }
} }
private async Task DownloadItems()
{
_itemsLoading = true;
await ItemDownloadingTask(Query, data =>
{
_items.AddRange(data);
if (data.Count() < 5)
{
_allItemsLoaded = true;
}
else
{
Query.After += 5;
}
_itemsLoading = false;
});
}
#endregion #endregion
} }

View File

@@ -4,6 +4,7 @@ using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Media; using WatchIt.Common.Model.Media;
using WatchIt.Common.Model.Movies; using WatchIt.Common.Model.Movies;
using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Photos;
using WatchIt.Common.Model.Rating;
using WatchIt.Common.Model.Series; using WatchIt.Common.Model.Series;
using WatchIt.Website.Layout; using WatchIt.Website.Layout;
using WatchIt.Website.Services.Utility.Authentication; using WatchIt.Website.Services.Utility.Authentication;
@@ -48,7 +49,7 @@ public partial class MediaPage : ComponentBase
private MediaPosterResponse? _poster; private MediaPosterResponse? _poster;
private IEnumerable<GenreResponse> _genres; private IEnumerable<GenreResponse> _genres;
private MediaRatingResponse _globalRating; private RatingResponse _globalRating;
private MovieResponse? _movie; private MovieResponse? _movie;
private SeriesResponse? _series; private SeriesResponse? _series;
@@ -123,7 +124,7 @@ public partial class MediaPage : ComponentBase
} }
else else
{ {
await MediaWebAPIService.PutMediaRating(Id, new MediaRatingRequest(rating)); await MediaWebAPIService.PutMediaRating(Id, new RatingRequest(rating));
_userRating = rating; _userRating = rating;
} }
await MediaWebAPIService.GetMediaRating(Id, data => _globalRating = data); await MediaWebAPIService.GetMediaRating(Id, data => _globalRating = data);

View File

@@ -33,8 +33,14 @@
<SearchResultComponent TItem="MovieResponse" <SearchResultComponent TItem="MovieResponse"
TQuery="MovieQueryParameters" TQuery="MovieQueryParameters"
Title="Movies" Title="Movies"
UrlIdTemplate="/media/{0}"
IdSource="@(item => item.Id)"
NameSource="@(item => item.Title)"
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
Query="@(new MovieQueryParameters { Title = DecodedQuery })" Query="@(new MovieQueryParameters { Title = DecodedQuery })"
DownloadingTask="MoviesWebAPIService.GetAllMovies"/> ItemDownloadingTask="@(MoviesWebAPIService.GetAllMovies)"
PictureDownloadingTask="@((id, action) => MediaWebAPIService.GetMediaPoster(id, action))"
RatingDownloadingTask="@((id, action) => MediaWebAPIService.GetMediaRating(id, action))"/>
</div> </div>
</div> </div>
<div class="row mt-3"> <div class="row mt-3">
@@ -42,8 +48,14 @@
<SearchResultComponent TItem="SeriesResponse" <SearchResultComponent TItem="SeriesResponse"
TQuery="SeriesQueryParameters" TQuery="SeriesQueryParameters"
Title="TV series" Title="TV series"
UrlIdTemplate="/media/{0}"
IdSource="@(item => item.Id)"
NameSource="@(item => item.Title)"
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
Query="@(new SeriesQueryParameters { Title = DecodedQuery })" Query="@(new SeriesQueryParameters { Title = DecodedQuery })"
DownloadingTask="SeriesWebAPIService.GetAllSeries"/> ItemDownloadingTask="@(SeriesWebAPIService.GetAllSeries)"
PictureDownloadingTask="@((id, action) => MediaWebAPIService.GetMediaPoster(id, action))"
RatingDownloadingTask="@((id, action) => MediaWebAPIService.GetMediaRating(id, action))"/>
</div> </div>
</div> </div>
} }

View File

@@ -1,6 +1,7 @@
using System.Net; using System.Net;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using WatchIt.Website.Layout; using WatchIt.Website.Layout;
using WatchIt.Website.Services.WebAPI.Media;
using WatchIt.Website.Services.WebAPI.Movies; using WatchIt.Website.Services.WebAPI.Movies;
using WatchIt.Website.Services.WebAPI.Series; using WatchIt.Website.Services.WebAPI.Series;
@@ -12,6 +13,7 @@ public partial class SearchPage : ComponentBase
[Inject] private IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!; [Inject] private IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!;
[Inject] private ISeriesWebAPIService SeriesWebAPIService { get; set; } = default!; [Inject] private ISeriesWebAPIService SeriesWebAPIService { get; set; } = default!;
[Inject] private IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
#endregion #endregion

View File

@@ -113,4 +113,8 @@ body, html {
.w-100 { .w-100 {
width: 100%; width: 100%;
}
.picture-aspect-ratio {
aspect-ratio: 3/5;
} }