UserPage - Lists of rated movies and tv series added

This commit is contained in:
2024-11-01 01:03:00 +01:00
Unverified
parent 2f6eb33518
commit 226b2b619c
38 changed files with 862 additions and 156 deletions

View File

@@ -18,7 +18,7 @@ public class GenreResponse : Genre, IQueryOrderable<GenreResponse>
[JsonPropertyName("id")] [JsonPropertyName("id")]
public long Id { get; set; } public short Id { get; set; }
#endregion #endregion

View File

@@ -54,6 +54,9 @@ public class MediaQueryParameters : QueryParameters<MediaResponse>
[FromQuery(Name = "rating_count_to")] [FromQuery(Name = "rating_count_to")]
public long? RatingCountTo { get; set; } public long? RatingCountTo { get; set; }
[FromQuery(Name = "genre")]
public IEnumerable<short>? Genres { get; set; }
#endregion #endregion
@@ -78,6 +81,8 @@ public class MediaQueryParameters : QueryParameters<MediaResponse>
TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo) TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo)
&& &&
TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo) TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo)
&&
TestContains(Genres, item.Genres.Select(x => x.Id))
); );
#endregion #endregion

View File

@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Rating; using WatchIt.Common.Model.Rating;
using WatchIt.Common.Query; using WatchIt.Common.Query;
@@ -31,6 +32,9 @@ public class MediaResponse : Media, IQueryOrderable<MediaResponse>
[JsonPropertyName("rating")] [JsonPropertyName("rating")]
public RatingResponse Rating { get; set; } public RatingResponse Rating { get; set; }
[JsonPropertyName("genres")]
public IEnumerable<GenreResponse> Genres { get; set; }
#endregion #endregion
@@ -52,6 +56,7 @@ public class MediaResponse : Media, IQueryOrderable<MediaResponse>
Length = media.Length; Length = media.Length;
Type = mediaType; Type = mediaType;
Rating = RatingResponse.Create(media.RatingMedia); Rating = RatingResponse.Create(media.RatingMedia);
Genres = media.Genres.Select(x => new GenreResponse(x)).ToList();
} }
#endregion #endregion

View File

@@ -60,6 +60,9 @@ public class MovieQueryParameters : QueryParameters<MovieResponse>
[FromQuery(Name = "rating_count_to")] [FromQuery(Name = "rating_count_to")]
public long? RatingCountTo { get; set; } public long? RatingCountTo { get; set; }
[FromQuery(Name = "genre")]
public IEnumerable<short>? Genres { get; set; }
#endregion #endregion
@@ -84,6 +87,8 @@ public class MovieQueryParameters : QueryParameters<MovieResponse>
TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo) TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo)
&& &&
TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo) TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo)
&&
TestContains(item.Genres.Select(x => x.Id), Genres)
); );
#endregion #endregion

View File

@@ -0,0 +1,104 @@
using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Query;
namespace WatchIt.Common.Model.Movies;
public class MovieRatedQueryParameters : QueryParameters<MovieRatedResponse>
{
#region PROPERTIES
[FromQuery(Name = "title")]
public string? Title { get; set; }
[FromQuery(Name = "original_title")]
public string? OriginalTitle { get; set; }
[FromQuery(Name = "description")]
public string? Description { get; set; }
[FromQuery(Name = "release_date")]
public DateOnly? ReleaseDate { get; set; }
[FromQuery(Name = "release_date_from")]
public DateOnly? ReleaseDateFrom { get; set; }
[FromQuery(Name = "release_date_to")]
public DateOnly? ReleaseDateTo { get; set; }
[FromQuery(Name = "length")]
public short? Length { get; set; }
[FromQuery(Name = "length_from")]
public short? LengthFrom { get; set; }
[FromQuery(Name = "length_to")]
public short? LengthTo { get; set; }
[FromQuery(Name = "budget")]
public decimal? Budget { get; set; }
[FromQuery(Name = "budget_from")]
public decimal? BudgetFrom { get; set; }
[FromQuery(Name = "budget_to")]
public decimal? BudgetTo { get; set; }
[FromQuery(Name = "rating_average")]
public decimal? RatingAverage { get; set; }
[FromQuery(Name = "rating_average_from")]
public decimal? RatingAverageFrom { get; set; }
[FromQuery(Name = "rating_average_to")]
public decimal? RatingAverageTo { get; set; }
[FromQuery(Name = "rating_count")]
public long? RatingCount { get; set; }
[FromQuery(Name = "rating_count_from")]
public long? RatingCountFrom { get; set; }
[FromQuery(Name = "rating_count_to")]
public long? RatingCountTo { get; set; }
[FromQuery(Name = "genre")]
public IEnumerable<short>? Genres { get; set; }
[FromQuery(Name = "user_rating")]
public decimal? UserRating { get; set; }
[FromQuery(Name = "user_rating_from")]
public decimal? UserRatingFrom { get; set; }
[FromQuery(Name = "user_rating_to")]
public decimal? UserRatingTo { get; set; }
#endregion
#region PRIVATE METHODS
protected override bool IsMeetingConditions(MovieRatedResponse item) =>
(
TestStringWithRegex(item.Title, Title)
&&
TestStringWithRegex(item.OriginalTitle, OriginalTitle)
&&
TestStringWithRegex(item.Description, Description)
&&
TestComparable(item.ReleaseDate, ReleaseDate, ReleaseDateFrom, ReleaseDateTo)
&&
TestComparable(item.Length, Length, LengthFrom, LengthTo)
&&
TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo)
&&
TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo)
&&
TestContains(Genres, item.Genres.Select(x => x.Id))
&&
TestComparable((decimal)item.UserRating, UserRating, UserRatingFrom, UserRatingTo)
);
#endregion
}

View File

@@ -0,0 +1,58 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Rating;
using WatchIt.Common.Query;
using WatchIt.Database.Model.Media;
using WatchIt.Database.Model.Rating;
namespace WatchIt.Common.Model.Movies;
public class MovieRatedResponse : MovieResponse, IQueryOrderable<MovieRatedResponse>
{
#region PROPERTIES
[JsonIgnore]
public static IDictionary<string, Func<MovieRatedResponse, IComparable>> OrderableProperties { get; } = new Dictionary<string, Func<MovieRatedResponse, IComparable>>
{
{ "id", x => x.Id },
{ "title", x => x.Title },
{ "original_title", x => x.OriginalTitle },
{ "description", x => x.Description },
{ "release_date", x => x.ReleaseDate },
{ "length", x => x.Length },
{ "budget", x => x.Budget },
{ "rating.average", x => x.Rating.Average },
{ "rating.count", x => x.Rating.Count },
{ "user_rating", x => x.UserRating }
};
[JsonPropertyName("user_rating")]
public short UserRating { get; set; }
#endregion
#region CONSTRUCTORS
[JsonConstructor]
public MovieRatedResponse() { }
[SetsRequiredMembers]
public MovieRatedResponse(MediaMovie mediaMovie, RatingMedia response)
{
Id = mediaMovie.Media.Id;
Title = mediaMovie.Media.Title;
OriginalTitle = mediaMovie.Media.OriginalTitle;
Description = mediaMovie.Media.Description;
ReleaseDate = mediaMovie.Media.ReleaseDate;
Length = mediaMovie.Media.Length;
Budget = mediaMovie.Budget;
Rating = RatingResponse.Create(mediaMovie.Media.RatingMedia);
Genres = mediaMovie.Media.Genres.Select(x => new GenreResponse(x)).ToList();
UserRating = response.Rating;
}
#endregion
}

View File

@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Rating; using WatchIt.Common.Model.Rating;
using WatchIt.Common.Query; using WatchIt.Common.Query;
using WatchIt.Database.Model.Media; using WatchIt.Database.Model.Media;
@@ -30,6 +31,9 @@ public class MovieResponse : Movie, IQueryOrderable<MovieResponse>
[JsonPropertyName("rating")] [JsonPropertyName("rating")]
public RatingResponse Rating { get; set; } public RatingResponse Rating { get; set; }
[JsonPropertyName("genres")]
public IEnumerable<GenreResponse> Genres { get; set; }
#endregion #endregion
@@ -51,6 +55,7 @@ public class MovieResponse : Movie, IQueryOrderable<MovieResponse>
Length = mediaMovie.Media.Length; Length = mediaMovie.Media.Length;
Budget = mediaMovie.Budget; Budget = mediaMovie.Budget;
Rating = RatingResponse.Create(mediaMovie.Media.RatingMedia); Rating = RatingResponse.Create(mediaMovie.Media.RatingMedia);
Genres = mediaMovie.Media.Genres.Select(x => new GenreResponse(x)).ToList();
} }
#endregion #endregion

View File

@@ -54,6 +54,9 @@ public class SeriesQueryParameters : QueryParameters<SeriesResponse>
[FromQuery(Name = "rating_count_to")] [FromQuery(Name = "rating_count_to")]
public long? RatingCountTo { get; set; } public long? RatingCountTo { get; set; }
[FromQuery(Name = "genre")]
public IEnumerable<short>? Genres { get; set; }
#endregion #endregion
@@ -78,6 +81,8 @@ public class SeriesQueryParameters : QueryParameters<SeriesResponse>
TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo) TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo)
&& &&
TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo) TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo)
&&
TestContains(item.Genres.Select(x => x.Id), Genres)
); );
#endregion #endregion

View File

@@ -0,0 +1,100 @@
using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Query;
namespace WatchIt.Common.Model.Series;
public class SeriesRatedQueryParameters : QueryParameters<SeriesRatedResponse>
{
#region PROPERTIES
[FromQuery(Name = "title")]
public string? Title { get; set; }
[FromQuery(Name = "original_title")]
public string? OriginalTitle { get; set; }
[FromQuery(Name = "description")]
public string? Description { get; set; }
[FromQuery(Name = "release_date")]
public DateOnly? ReleaseDate { get; set; }
[FromQuery(Name = "release_date_from")]
public DateOnly? ReleaseDateFrom { get; set; }
[FromQuery(Name = "release_date_to")]
public DateOnly? ReleaseDateTo { get; set; }
[FromQuery(Name = "length")]
public short? Length { get; set; }
[FromQuery(Name = "length_from")]
public short? LengthFrom { get; set; }
[FromQuery(Name = "length_to")]
public short? LengthTo { get; set; }
[FromQuery(Name = "has_ended")]
public bool? HasEnded { get; set; }
[FromQuery(Name = "rating_average")]
public decimal? RatingAverage { get; set; }
[FromQuery(Name = "rating_average_from")]
public decimal? RatingAverageFrom { get; set; }
[FromQuery(Name = "rating_average_to")]
public decimal? RatingAverageTo { get; set; }
[FromQuery(Name = "rating_count")]
public long? RatingCount { get; set; }
[FromQuery(Name = "rating_count_from")]
public long? RatingCountFrom { get; set; }
[FromQuery(Name = "rating_count_to")]
public long? RatingCountTo { get; set; }
[FromQuery(Name = "genre")]
public IEnumerable<short>? Genres { get; set; }
[FromQuery(Name = "user_rating")]
public decimal? UserRating { get; set; }
[FromQuery(Name = "user_rating_from")]
public decimal? UserRatingFrom { get; set; }
[FromQuery(Name = "user_rating_to")]
public decimal? UserRatingTo { get; set; }
#endregion
#region PRIVATE METHODS
protected override bool IsMeetingConditions(SeriesRatedResponse item) =>
(
TestStringWithRegex(item.Title, Title)
&&
TestStringWithRegex(item.OriginalTitle, OriginalTitle)
&&
TestStringWithRegex(item.Description, Description)
&&
TestComparable(item.ReleaseDate, ReleaseDate, ReleaseDateFrom, ReleaseDateTo)
&&
TestComparable(item.Length, Length, LengthFrom, LengthTo)
&&
Test(item.HasEnded, HasEnded)
&&
TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo)
&&
TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo)
&&
TestContains(item.Genres.Select(x => x.Id), Genres)
&&
TestComparable((decimal)item.UserRating, UserRating, UserRatingFrom, UserRatingTo)
);
#endregion
}

View File

@@ -0,0 +1,58 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Rating;
using WatchIt.Common.Query;
using WatchIt.Database.Model.Media;
using WatchIt.Database.Model.Rating;
namespace WatchIt.Common.Model.Series;
public class SeriesRatedResponse : SeriesResponse, IQueryOrderable<SeriesRatedResponse>
{
#region PROPERTIES
[JsonIgnore]
public static IDictionary<string, Func<SeriesRatedResponse, IComparable>> OrderableProperties { get; } = new Dictionary<string, Func<SeriesRatedResponse, IComparable>>
{
{ "id", x => x.Id },
{ "title", x => x.Title },
{ "original_title", x => x.OriginalTitle },
{ "description", x => x.Description },
{ "release_date", x => x.ReleaseDate },
{ "length", x => x.Length },
{ "has_ended", x => x.HasEnded },
{ "rating.average", x => x.Rating.Average },
{ "rating.count", x => x.Rating.Count },
{ "user_rating", x => x.UserRating }
};
[JsonPropertyName("user_rating")]
public short UserRating { get; set; }
#endregion
#region CONSTRUCTORS
[JsonConstructor]
public SeriesRatedResponse() { }
[SetsRequiredMembers]
public SeriesRatedResponse(MediaSeries mediaSeries, RatingMedia response)
{
Id = mediaSeries.Media.Id;
Title = mediaSeries.Media.Title;
OriginalTitle = mediaSeries.Media.OriginalTitle;
Description = mediaSeries.Media.Description;
ReleaseDate = mediaSeries.Media.ReleaseDate;
Length = mediaSeries.Media.Length;
HasEnded = mediaSeries.HasEnded;
Rating = RatingResponse.Create(mediaSeries.Media.RatingMedia);
Genres = mediaSeries.Media.Genres.Select(x => new GenreResponse(x)).ToList();
UserRating = response.Rating;
}
#endregion
}

View File

@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using WatchIt.Common.Model.Genres;
using WatchIt.Common.Model.Rating; using WatchIt.Common.Model.Rating;
using WatchIt.Common.Query; using WatchIt.Common.Query;
using WatchIt.Database.Model.Media; using WatchIt.Database.Model.Media;
@@ -30,6 +31,9 @@ public class SeriesResponse : Series, IQueryOrderable<SeriesResponse>
[JsonPropertyName("rating")] [JsonPropertyName("rating")]
public RatingResponse Rating { get; set; } public RatingResponse Rating { get; set; }
[JsonPropertyName("genres")]
public IEnumerable<GenreResponse> Genres { get; set; }
#endregion #endregion
@@ -51,6 +55,7 @@ public class SeriesResponse : Series, IQueryOrderable<SeriesResponse>
Length = mediaSeries.Media.Length; Length = mediaSeries.Media.Length;
HasEnded = mediaSeries.HasEnded; HasEnded = mediaSeries.HasEnded;
Rating = RatingResponse.Create(mediaSeries.Media.RatingMedia); Rating = RatingResponse.Create(mediaSeries.Media.RatingMedia);
Genres = mediaSeries.Media.Genres.Select(x => new GenreResponse(x)).ToList();
} }
#endregion #endregion

View File

@@ -1,4 +1,5 @@
using System.Globalization; using System.Collections;
using System.Globalization;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@@ -39,13 +40,16 @@ public abstract class QueryParameters
FromQueryAttribute? attribute = property.GetCustomAttributes<FromQueryAttribute>(true).FirstOrDefault(); FromQueryAttribute? attribute = property.GetCustomAttributes<FromQueryAttribute>(true).FirstOrDefault();
if (value is not null && attribute is not null) if (value is not null && attribute is not null)
{ {
string valueString = (value switch if (value is IEnumerable enumerable and not string)
{ {
decimal d => d.ToString(CultureInfo.InvariantCulture), IEnumerable<string> arrayQueryElements = enumerable.Cast<object>().Select(x => QueryElementToString(attribute.Name!, x.ToString()));
_ => value.ToString() queries.AddRange(arrayQueryElements);
})!; }
string query = $"{attribute.Name}={valueString}"; else
queries.Add(query); {
string query = QueryElementToString(attribute.Name!, value);
queries.Add(query);
}
} }
} }
@@ -58,6 +62,18 @@ public abstract class QueryParameters
#region PRIVATE METHODS #region PRIVATE METHODS
private string QueryElementToString(string name, object value)
{
string valueString = (value switch
{
decimal d => d.ToString(CultureInfo.InvariantCulture),
_ => value.ToString()
})!;
string query = $"{name}={valueString}";
return query;
}
protected static bool Test<T>(T? property, T? query) => protected static bool Test<T>(T? property, T? query) =>
( (
query is null query is null
@@ -113,6 +129,15 @@ public abstract class QueryParameters
) )
); );
protected static bool TestContains<T>(IEnumerable<T>? shouldBeInCollection, IEnumerable<T>? collection) =>
(
collection is null
||
shouldBeInCollection is null
||
shouldBeInCollection.All(collection.Contains)
);
#endregion #endregion
} }

View File

@@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Accounts;
using WatchIt.Common.Model.Movies;
using WatchIt.Common.Model.Series;
using WatchIt.WebAPI.Services.Controllers.Accounts; using WatchIt.WebAPI.Services.Controllers.Accounts;
namespace WatchIt.WebAPI.Controllers; namespace WatchIt.WebAPI.Controllers;
@@ -49,17 +51,22 @@ public class AccountsController(IAccountsControllerService accountsControllerSer
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetAccountInfo([FromRoute]long id) => await accountsControllerService.GetAccountInfo(id); public async Task<ActionResult> GetAccountInfo([FromRoute]long id) => await accountsControllerService.GetAccountInfo(id);
[HttpGet("info")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetAccountInfo() => await accountsControllerService.GetAccountInfo();
[HttpPut("info")] [HttpPut("info")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> PutAccountInfo([FromBody]AccountRequest data) => await accountsControllerService.PutAccountInfo(data); public async Task<ActionResult> PutAccountInfo([FromBody]AccountRequest data) => await accountsControllerService.PutAccountInfo(data);
[HttpGet("{id}/movies")]
[AllowAnonymous]
[ProducesResponseType(typeof(IEnumerable<MovieRatedResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetAccountRatedMovies([FromRoute]long id, MovieRatedQueryParameters query) => await accountsControllerService.GetAccountRatedMovies(id, query);
[HttpGet("{id}/series")]
[AllowAnonymous]
[ProducesResponseType(typeof(IEnumerable<SeriesRatedResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetAccountRatedSeries([FromRoute]long id, SeriesRatedQueryParameters query) => await accountsControllerService.GetAccountRatedSeries(id, query);
} }

View File

@@ -5,8 +5,13 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SimpleToolkit.Extensions; using SimpleToolkit.Extensions;
using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Accounts;
using WatchIt.Common.Model.Media;
using WatchIt.Common.Model.Movies;
using WatchIt.Common.Model.Series;
using WatchIt.Database; using WatchIt.Database;
using WatchIt.Database.Model.Account; using WatchIt.Database.Model.Account;
using WatchIt.Database.Model.Media;
using WatchIt.Database.Model.Rating;
using WatchIt.WebAPI.Services.Controllers.Common; using WatchIt.WebAPI.Services.Controllers.Common;
using WatchIt.WebAPI.Services.Utility.Tokens; using WatchIt.WebAPI.Services.Utility.Tokens;
using WatchIt.WebAPI.Services.Utility.Tokens.Exceptions; using WatchIt.WebAPI.Services.Utility.Tokens.Exceptions;
@@ -136,7 +141,6 @@ public class AccountsControllerService(
return RequestResult.Ok(picture); return RequestResult.Ok(picture);
} }
public async Task<RequestResult> GetAccountInfo() => await GetAccountInfo(userService.GetUserId());
public async Task<RequestResult> GetAccountInfo(long id) public async Task<RequestResult> GetAccountInfo(long id)
{ {
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id); Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
@@ -160,6 +164,32 @@ public class AccountsControllerService(
data.UpdateAccount(account); data.UpdateAccount(account);
return RequestResult.Ok(); return RequestResult.Ok();
} }
public async Task<RequestResult> GetAccountRatedMovies(long id, MovieRatedQueryParameters query)
{
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
if (account is null)
{
return RequestResult.NotFound();
}
IEnumerable<MovieRatedResponse> response = account.RatingMedia.Join(database.MediaMovies, x => x.MediaId, x => x.Id, (x, y) => new MovieRatedResponse(y, x));
response = query.PrepareData(response);
return RequestResult.Ok(response);
}
public async Task<RequestResult> GetAccountRatedSeries(long id, SeriesRatedQueryParameters query)
{
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
if (account is null)
{
return RequestResult.NotFound();
}
IEnumerable<SeriesRatedResponse> response = account.RatingMedia.Join(database.MediaSeries, x => x.MediaId, x => x.Id, (x, y) => new SeriesRatedResponse(y, x));
response = query.PrepareData(response);
return RequestResult.Ok(response);
}
#endregion #endregion

View File

@@ -1,4 +1,7 @@
using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Accounts;
using WatchIt.Common.Model.Media;
using WatchIt.Common.Model.Movies;
using WatchIt.Common.Model.Series;
using WatchIt.WebAPI.Services.Controllers.Common; using WatchIt.WebAPI.Services.Controllers.Common;
namespace WatchIt.WebAPI.Services.Controllers.Accounts; namespace WatchIt.WebAPI.Services.Controllers.Accounts;
@@ -10,7 +13,8 @@ public interface IAccountsControllerService
Task<RequestResult> AuthenticateRefresh(); Task<RequestResult> AuthenticateRefresh();
Task<RequestResult> Logout(); Task<RequestResult> Logout();
Task<RequestResult> GetAccountProfilePicture(long id); Task<RequestResult> GetAccountProfilePicture(long id);
Task<RequestResult> GetAccountInfo();
Task<RequestResult> GetAccountInfo(long id); Task<RequestResult> GetAccountInfo(long id);
Task<RequestResult> PutAccountInfo(AccountRequest data); Task<RequestResult> PutAccountInfo(AccountRequest data);
Task<RequestResult> GetAccountRatedMovies(long id, MovieRatedQueryParameters query);
Task<RequestResult> GetAccountRatedSeries(long id, SeriesRatedQueryParameters query);
} }

View File

@@ -1,4 +1,6 @@
using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Accounts;
using WatchIt.Common.Model.Movies;
using WatchIt.Common.Model.Series;
using WatchIt.Common.Services.HttpClient; using WatchIt.Common.Services.HttpClient;
using WatchIt.Website.Services.Configuration; using WatchIt.Website.Services.Configuration;
using WatchIt.Website.Services.Tokens; using WatchIt.Website.Services.Tokens;
@@ -78,9 +80,9 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig
.ExecuteAction(); .ExecuteAction();
} }
public async Task GetAccountInfoById(long id, Action<AccountResponse>? successAction = null, Action? notFoundAction = null) public async Task GetAccountInfo(long id, Action<AccountResponse>? successAction = null, Action? notFoundAction = null)
{ {
string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountInfoById, id); string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountInfo, id);
HttpRequest request = new HttpRequest(HttpMethodType.Get, url); HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
HttpResponse response = await httpClientService.SendRequestAsync(request); HttpResponse response = await httpClientService.SendRequestAsync(request);
@@ -89,18 +91,6 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig
.ExecuteAction(); .ExecuteAction();
} }
public async Task GetAccountInfo(Action<AccountResponse>? successAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null)
{
string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountInfo);
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor401Unauthorized(unauthorizedAction)
.RegisterActionFor404NotFound(notFoundAction)
.ExecuteAction();
}
public async Task PutAccountInfo(AccountRequest data, Action<AccountResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null) public async Task PutAccountInfo(AccountRequest data, Action<AccountResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null)
{ {
string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountInfo); string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountInfo);
@@ -117,6 +107,34 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig
.ExecuteAction(); .ExecuteAction();
} }
public async Task GetAccountRatedMovies(long id, MovieRatedQueryParameters query, Action<IEnumerable<MovieRatedResponse>>? successAction = null, Action? notFoundAction = null)
{
string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountRatedMovies, id);
HttpRequest request = new HttpRequest(HttpMethodType.Get, url)
{
Query = query
};
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor404NotFound(notFoundAction)
.ExecuteAction();
}
public async Task GetAccountRatedSeries(long id, SeriesRatedQueryParameters query, Action<IEnumerable<SeriesRatedResponse>>? successAction = null, Action? notFoundAction = null)
{
string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountRatedSeries, id);
HttpRequest request = new HttpRequest(HttpMethodType.Get, url)
{
Query = query
};
HttpResponse response = await httpClientService.SendRequestAsync(request);
response.RegisterActionFor2XXSuccess(successAction)
.RegisterActionFor404NotFound(notFoundAction)
.ExecuteAction();
}
#endregion #endregion

View File

@@ -1,4 +1,6 @@
using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Accounts;
using WatchIt.Common.Model.Movies;
using WatchIt.Common.Model.Series;
namespace WatchIt.Website.Services.Client.Accounts; namespace WatchIt.Website.Services.Client.Accounts;
@@ -9,7 +11,8 @@ public interface IAccountsClientService
Task AuthenticateRefresh(Action<AuthenticateResponse>? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null); Task AuthenticateRefresh(Action<AuthenticateResponse>? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
Task Logout(Action? successAction = null); Task Logout(Action? successAction = null);
Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null); Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
Task GetAccountInfoById(long id, Action<AccountResponse>? successAction = null, Action? notFoundAction = null); Task GetAccountInfo(long id, Action<AccountResponse>? successAction = null, Action? notFoundAction = null);
Task GetAccountInfo(Action<AccountResponse>? successAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null);
Task PutAccountInfo(AccountRequest data, Action<AccountResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null); Task PutAccountInfo(AccountRequest data, Action<AccountResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null);
Task GetAccountRatedMovies(long id, MovieRatedQueryParameters query, Action<IEnumerable<MovieRatedResponse>>? successAction = null, Action? notFoundAction = null);
Task GetAccountRatedSeries(long id, SeriesRatedQueryParameters query, Action<IEnumerable<SeriesRatedResponse>>? successAction = null, Action? notFoundAction = null);
} }

View File

@@ -8,7 +8,8 @@ public class Accounts
public string AuthenticateRefresh { get; set; } public string AuthenticateRefresh { get; set; }
public string Logout { get; set; } public string Logout { get; set; }
public string GetProfilePicture { get; set; } public string GetProfilePicture { get; set; }
public string GetAccountInfoById { get; set; }
public string GetAccountInfo { get; set; } public string GetAccountInfo { get; set; }
public string PutAccountInfo { get; set; } public string PutAccountInfo { get; set; }
public string GetAccountRatedMovies { get; set; }
public string GetAccountRatedSeries { get; set; }
} }

View File

@@ -13,7 +13,7 @@
<link rel="stylesheet" href="css/panel.css?version=0.3.0.3"/> <link rel="stylesheet" href="css/panel.css?version=0.3.0.3"/>
<link rel="stylesheet" href="css/main_button.css?version=0.3.0.0"/> <link rel="stylesheet" href="css/main_button.css?version=0.3.0.0"/>
<link rel="stylesheet" href="css/gaps.css?version=0.3.0.1"/> <link rel="stylesheet" href="css/gaps.css?version=0.3.0.1"/>
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.4.0.4"/> <link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.4.0.12"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<!-- BOOTSTRAP --> <!-- BOOTSTRAP -->

View File

@@ -1,14 +1,14 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using WatchIt.Common.Query; using WatchIt.Common.Query;
namespace WatchIt.Website.Components.Pages.DatabasePage.Subcomponents; namespace WatchIt.Website.Components.Common.ListComponent;
public abstract class FilterFormComponent<TItem, TQuery> : ComponentBase where TItem : IQueryOrderable<TItem> where TQuery : QueryParameters<TItem> public abstract class FilterFormComponent<TItem, TQuery> : ComponentBase where TItem : IQueryOrderable<TItem> where TQuery : QueryParameters<TItem>
{ {
#region PARAMETERS #region PARAMETERS
[CascadingParameter] [CascadingParameter]
protected DatabasePageComponent<TItem, TQuery> Parent { get; set; } protected ListComponent<TItem, TQuery> Parent { get; set; }
#endregion #endregion

View File

@@ -60,6 +60,8 @@
PosterPlaceholder="@(PosterPlaceholder)" PosterPlaceholder="@(PosterPlaceholder)"
PosterDownloadingTask="@(action => PictureDownloadingTask(id, action))" PosterDownloadingTask="@(action => PictureDownloadingTask(id, action))"
GlobalRating="@(RatingSource(item))" GlobalRating="@(RatingSource(item))"
SecondaryRating="@(SecondaryRatingSource?.Invoke(item))"
SecondaryRatingTitle="@(SecondaryRatingTitle)"
GetGlobalRatingMethod="@(action => GetGlobalRatingMethod(id, action))" GetGlobalRatingMethod="@(action => GetGlobalRatingMethod(id, action))"
GetUserRatingMethod="@(GetUserRatingMethod is not null ? (user, actionSuccess, actionNotFound) => GetUserRatingMethod(id, user, actionSuccess, actionNotFound) : null)" GetUserRatingMethod="@(GetUserRatingMethod is not null ? (user, actionSuccess, actionNotFound) => GetUserRatingMethod(id, user, actionSuccess, actionNotFound) : null)"
PutRatingMethod="@(PutRatingMethod is not null ? (request) => PutRatingMethod(id, request) : null)" PutRatingMethod="@(PutRatingMethod is not null ? (request) => PutRatingMethod(id, request) : null)"

View File

@@ -4,9 +4,9 @@ using WatchIt.Common.Model.Movies;
using WatchIt.Common.Model.Rating; using WatchIt.Common.Model.Rating;
using WatchIt.Common.Query; using WatchIt.Common.Query;
namespace WatchIt.Website.Components.Pages.DatabasePage; namespace WatchIt.Website.Components.Common.ListComponent;
public partial class DatabasePageComponent<TItem, TQuery> : ComponentBase where TItem : IQueryOrderable<TItem> where TQuery : QueryParameters<TItem> public partial class ListComponent<TItem, TQuery> : ComponentBase where TItem : IQueryOrderable<TItem> where TQuery : QueryParameters<TItem>
{ {
#region SERVICES #region SERVICES
@@ -23,6 +23,8 @@ public partial class DatabasePageComponent<TItem, TQuery> : ComponentBase where
[Parameter] public required Func<TItem, string> NameSource { get; set; } [Parameter] public required Func<TItem, string> NameSource { get; set; }
[Parameter] public Func<TItem, string?> AdditionalNameInfoSource { get; set; } = _ => null; [Parameter] public Func<TItem, string?> AdditionalNameInfoSource { get; set; } = _ => null;
[Parameter] public required Func<TItem, RatingResponse> RatingSource { get; set; } [Parameter] public required Func<TItem, RatingResponse> RatingSource { get; set; }
[Parameter] public Func<TItem, short?>? SecondaryRatingSource { get; set; }
[Parameter] public string? SecondaryRatingTitle { get; set; }
[Parameter] public required string UrlIdTemplate { get; set; } [Parameter] public required string UrlIdTemplate { get; set; }
[Parameter] public required Func<long, Action<Picture>, Task> PictureDownloadingTask { get; set; } [Parameter] public required Func<long, Action<Picture>, Task> PictureDownloadingTask { get; set; }
[Parameter] public required Func<TQuery, Action<IEnumerable<TItem>>, Task> ItemDownloadingTask { get; set; } [Parameter] public required Func<TQuery, Action<IEnumerable<TItem>>, Task> ItemDownloadingTask { get; set; }

View File

@@ -5,14 +5,18 @@
<i id="star" class="fas fa-star"></i> <i id="star" class="fas fa-star"></i>
} }
<div class="vstack"> <div class="vstack">
@if (Rating is not null && Rating.Count > 0) @if (SingleRating is not null)
{
<span id="ratingSingleLine">@($"{SingleRating}/10")</span>
}
else if (Rating is not null && Rating.Count > 0)
{ {
<span id="ratingAverage">@($"{Math.Round(Rating.Average, 2)}/10")</span> <span id="ratingAverage">@($"{Math.Round(Rating.Average, 2)}/10")</span>
<span id="ratingCount">@(Rating.Count)</span> <span id="ratingCount">@(Rating.Count)</span>
} }
else else
{ {
<div id="ratingNoItems"> <div id="ratingSingleLine">
@if (Rating is null) @if (Rating is null)
{ {
<span><div class="spinner-border spinner-border-sm"></div>/10</span> <span><div class="spinner-border spinner-border-sm"></div>/10</span>
@@ -40,7 +44,7 @@
font-size: @((1.3 * Scale).ToCultureInvariantString())rem; font-size: @((1.3 * Scale).ToCultureInvariantString())rem;
} }
#ratingNoItems { #ratingSingleLine {
font-size: @((1.3 * Scale).ToCultureInvariantString())rem; font-size: @((1.3 * Scale).ToCultureInvariantString())rem;
} }

View File

@@ -8,6 +8,7 @@ public partial class DisplayRatingComponent : ComponentBase
#region PARAMETERS #region PARAMETERS
[Parameter] public RatingResponse? Rating { get; set; } [Parameter] public RatingResponse? Rating { get; set; }
[Parameter] public short? SingleRating { get; set; }
[Parameter] public DisplayRatingComponentEmptyMode EmptyMode { get; set; } = DisplayRatingComponentEmptyMode.NoRatings; [Parameter] public DisplayRatingComponentEmptyMode EmptyMode { get; set; } = DisplayRatingComponentEmptyMode.NoRatings;
[Parameter] public double Scale { get; set; } = 1; [Parameter] public double Scale { get; set; } = 1;

View File

@@ -12,40 +12,66 @@
<strong>@(Name)</strong>@(string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo) <strong>@(Name)</strong>@(string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo)
</a> </a>
</div> </div>
<div class="d-inline-flex gap-3"> <table id="ratingTable" class="table table-transparent table-bordered align-middle m-0">
<a class="text-reset text-decoration-none" href="@(ItemUrl)"> <thead>
<div class="vstack gap-2"> <tr>
<span id="ratingNameText">Global rating:</span> <th scope="col">
<DisplayRatingComponent Rating="GlobalRating" <span class="rating-name-text">Global rating:</span>
EmptyMode="DisplayRatingComponent.DisplayRatingComponentEmptyMode.DoubleDash" </th>
Scale="0.85"/> @if (SecondaryRating is not null)
</div> {
</a> <th scope="col">
@if (GetUserRatingMethod is not null && PutRatingMethod is not null && DeleteRatingMethod is not null) <span class="rating-name-text">@(SecondaryRatingTitle):</span>
{ </th>
<div class="vr"></div> }
<div class="vstack gap-2"> @if (GetUserRatingMethod is not null && PutRatingMethod is not null && DeleteRatingMethod is not null)
<span id="ratingNameText">Your rating:</span> {
<div class="d-inline-flex align-items-center h-100"> <th class scope="col">
@if (_user is null) <span class="rating-name-text">Your rating:</span>
{ </th>
<span id="ratingLoginInfoText">You must be logged in to rate</span> }
} </tr>
else if (!_userRatingLoaded) </thead>
{ <tbody>
<div class="d-flex align-items-center gap-2"> <tr>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> <td>
<span>Loading...</span> <DisplayRatingComponent Rating="@(GlobalRating)"
EmptyMode="DisplayRatingComponent.DisplayRatingComponentEmptyMode.DoubleDash"
Scale="0.85"/>
</td>
@if (SecondaryRating is not null)
{
<td>
<DisplayRatingComponent SingleRating="@(SecondaryRating)"
EmptyMode="DisplayRatingComponent.DisplayRatingComponentEmptyMode.DoubleDash"
Scale="0.85"/>
</td>
}
@if (GetUserRatingMethod is not null && PutRatingMethod is not null && DeleteRatingMethod is not null)
{
<td>
<div class="d-inline-flex align-items-center h-100">
@if (_user is null)
{
<span id="ratingLoginInfoText">You must be logged in to rate</span>
}
else if (!_userRatingLoaded)
{
<div class="d-flex align-items-center gap-2">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<span>Loading...</span>
</div>
}
else
{
<Rating Color="Color.Light" MaxValue="10" @bind-SelectedValue="@(_userRating)" @onclick="@(RatingChanged)"/>
}
</div> </div>
} </td>
else }
{ </tr>
<Rating Color="Color.Light" MaxValue="10" @bind-SelectedValue="@(_userRating)" @onclick="@(RatingChanged)"/> </tbody>
} </table>
</div>
</div>
}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -27,6 +27,8 @@ public partial class ListItemComponent : ComponentBase
[Parameter] public required Func<Action<Picture>, Task> PosterDownloadingTask { get; set; } [Parameter] public required Func<Action<Picture>, Task> PosterDownloadingTask { get; set; }
[Parameter] public RatingResponse? GlobalRating { get; set; } [Parameter] public RatingResponse? GlobalRating { get; set; }
[Parameter] public short? SecondaryRating { get; set; }
[Parameter] public string? SecondaryRatingTitle { get; set; }
[Parameter] public required Func<Action<RatingResponse>, Task> GetGlobalRatingMethod { get; set; } [Parameter] public required Func<Action<RatingResponse>, Task> GetGlobalRatingMethod { get; set; }
[Parameter] public Func<long, Action<short>, Action, Task>? GetUserRatingMethod { get; set; } [Parameter] public Func<long, Action<short>, Action, Task>? GetUserRatingMethod { get; set; }
[Parameter] public Func<RatingRequest, Task>? PutRatingMethod { get; set; } [Parameter] public Func<RatingRequest, Task>? PutRatingMethod { get; set; }

View File

@@ -1,10 +1,39 @@
/* IDS */ /* TAGS */
#ratingNameText { tbody, td, tfoot, th, thead, tr {
font-size: 14px; border-color: inherit;
font-weight: bold; border-style: unset;
border-width: 0;
} }
/* IDS */
#ratingLoginInfoText { #ratingLoginInfoText {
font-size: 13px; font-size: 13px;
}
#ratingTable {
width: fit-content;
border-spacing: unset;
border-collapse: separate;
margin-left: -0.5rem !important;
}
#ratingTable > thead > tr > th:not(:last-child) {
border-right: 1px solid #444;
}
#ratingTable > tbody > tr > td:not(:last-child) {
border-right: 1px solid #444;
}
/* CLASSES */
.rating-name-text {
font-size: 14px;
font-weight: bold;
} }

View File

@@ -1,4 +1,4 @@
@inherits FilterFormComponent<WatchIt.Common.Model.Movies.MovieResponse, WatchIt.Common.Model.Movies.MovieQueryParameters> @inherits WatchIt.Website.Components.Common.ListComponent.FilterFormComponent<WatchIt.Common.Model.Movies.MovieResponse, WatchIt.Common.Model.Movies.MovieQueryParameters>

View File

@@ -1,5 +1,5 @@
@using WatchIt.Common.Model.Genders @using WatchIt.Common.Model.Genders
@inherits FilterFormComponent<WatchIt.Common.Model.Persons.PersonResponse, WatchIt.Common.Model.Persons.PersonQueryParameters> @inherits WatchIt.Website.Components.Common.ListComponent.FilterFormComponent<WatchIt.Common.Model.Persons.PersonResponse, WatchIt.Common.Model.Persons.PersonQueryParameters>

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using WatchIt.Common.Model.Genders; using WatchIt.Common.Model.Genders;
using WatchIt.Common.Model.Persons; using WatchIt.Common.Model.Persons;
using WatchIt.Website.Components.Common.ListComponent;
using WatchIt.Website.Services.Client.Genders; using WatchIt.Website.Services.Client.Genders;
namespace WatchIt.Website.Components.Pages.DatabasePage.Subcomponents; namespace WatchIt.Website.Components.Pages.DatabasePage.Subcomponents;

View File

@@ -1,4 +1,4 @@
@inherits FilterFormComponent<WatchIt.Common.Model.Series.SeriesResponse, WatchIt.Common.Model.Series.SeriesQueryParameters> @inherits WatchIt.Website.Components.Common.ListComponent.FilterFormComponent<WatchIt.Common.Model.Series.SeriesResponse, WatchIt.Common.Model.Series.SeriesQueryParameters>
<EditForm Model="@(Query)"> <EditForm Model="@(Query)">
<div class="container-grid"> <div class="container-grid">

View File

@@ -0,0 +1,74 @@
@inherits WatchIt.Website.Components.Common.ListComponent.FilterFormComponent<WatchIt.Common.Model.Movies.MovieRatedResponse, WatchIt.Common.Model.Movies.MovieRatedQueryParameters>
<EditForm Model="@(Query)">
<div class="container-grid">
<div class="row mb-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Title</span>
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Title)"></InputText>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Original title</span>
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.OriginalTitle)"/>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Description</span>
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Description)"/>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Release date</span>
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateFrom)"/>
<span class="col-auto input-group-text">-</span>
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateTo)"/>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Length</span>
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthFrom)"/>
<span class="col-auto input-group-text">-</span>
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthTo)"/>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Budget</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" @bind-Value="@(Query.BudgetFrom)"/>
<span class="col-auto input-group-text">-</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" @bind-Value="@(Query.BudgetTo)"/>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Rating (count)</span>
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountFrom)"/>
<span class="col-auto input-group-text">-</span>
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountTo)"/>
</div>
</div>
<div class="row mt-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Rating (average)</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageFrom)"/>
<span class="col-auto input-group-text">-</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageTo)"/>
</div>
</div>
<div class="row mt-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">User rating</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="1" @bind-Value="@(Query.UserRatingFrom)"/>
<span class="col-auto input-group-text">-</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="1" @bind-Value="@(Query.UserRatingTo)"/>
</div>
</div>
</div>
</EditForm>

View File

@@ -0,0 +1,77 @@
@inherits WatchIt.Website.Components.Common.ListComponent.FilterFormComponent<WatchIt.Common.Model.Series.SeriesRatedResponse, WatchIt.Common.Model.Series.SeriesRatedQueryParameters>
<EditForm Model="@(Query)">
<div class="container-grid">
<div class="row mb-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Title</span>
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Title)"></InputText>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Original title</span>
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.OriginalTitle)"/>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Description</span>
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Description)"/>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Release date</span>
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateFrom)"/>
<span class="col-auto input-group-text">-</span>
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateTo)"/>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Length</span>
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthFrom)"/>
<span class="col-auto input-group-text">-</span>
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthTo)"/>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Has ended</span>
<div class="btn-group col">
<input type="radio" class="btn-check" name="has_ended" id="has_ended_yes" autocomplete="off" @onclick="() => Query.HasEnded = true">
<label class="btn btn-outline-secondary btn-sm" for="has_ended_yes">Yes</label>
<input type="radio" class="btn-check" name="has_ended" id="has_ended_no_choice" autocomplete="off" @onclick="() => Query.HasEnded = null" checked>
<label class="btn btn-outline-secondary btn-sm" for="has_ended_no_choice">No choice</label>
<input type="radio" class="btn-check" name="has_ended" id="has_ended_no" autocomplete="off" @onclick="() => Query.HasEnded = false">
<label class="btn btn-outline-secondary btn-sm" for="has_ended_no">No</label>
</div>
</div>
</div>
<div class="row my-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Rating (count)</span>
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountFrom)"/>
<span class="col-auto input-group-text">-</span>
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountTo)"/>
</div>
</div>
<div class="row mt-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">Rating (average)</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageFrom)"/>
<span class="col-auto input-group-text">-</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageTo)"/>
</div>
</div>
<div class="row mt-1">
<div class="input-group input-group-sm">
<span class="col-3 input-group-text">User rating</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="1" @bind-Value="@(Query.UserRatingFrom)"/>
<span class="col-auto input-group-text">-</span>
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="1" @bind-Value="@(Query.UserRatingTo)"/>
</div>
</div>
</div>
</EditForm>

View File

@@ -79,7 +79,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row pb-3">
<div class="col"> <div class="col">
@Body @Body
</div> </div>

View File

@@ -3,6 +3,7 @@
@using WatchIt.Common.Model.Series @using WatchIt.Common.Model.Series
@using WatchIt.Website.Components.Pages.DatabasePage @using WatchIt.Website.Components.Pages.DatabasePage
@using WatchIt.Website.Components.Pages.DatabasePage.Subcomponents @using WatchIt.Website.Components.Pages.DatabasePage.Subcomponents
@using WatchIt.Website.Components.Common.ListComponent
@page "/database/{type?}" @page "/database/{type?}"
@@ -30,78 +31,78 @@
@switch (Type) @switch (Type)
{ {
case "movies": case "movies":
<DatabasePageComponent TItem="MovieResponse" <ListComponent TItem="MovieResponse"
TQuery="MovieQueryParameters" TQuery="MovieQueryParameters"
Title="Movies database" Title="Movies database"
IdSource="@(item => item.Id)" IdSource="@(item => item.Id)"
NameSource="@(item => item.Title)" NameSource="@(item => item.Title)"
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)" AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
RatingSource="@(item => item.Rating)" RatingSource="@(item => item.Rating)"
UrlIdTemplate="/media/{0}" UrlIdTemplate="/media/{0}"
PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))" PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
ItemDownloadingTask="@(MoviesClientService.GetAllMovies)" ItemDownloadingTask="@(MoviesClientService.GetAllMovies)"
SortingOptions="@(new Dictionary<string, string> SortingOptions="@(new Dictionary<string, string>
{ {
{ "rating.count", "Number of ratings" }, { "rating.count", "Number of ratings" },
{ "rating.average", "Average rating" }, { "rating.average", "Average rating" },
{ "title", "Title" }, { "title", "Title" },
{ "release_date", "Release date" }, { "release_date", "Release date" },
})" })"
PosterPlaceholder="/assets/media_poster.png" PosterPlaceholder="/assets/media_poster.png"
GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))" GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))"
GetUserRatingMethod="@((id, userId, successAction, notfoundAction) => MediaClientService.GetMediaRatingByUser(id, userId, successAction, notfoundAction))" GetUserRatingMethod="@((id, userId, successAction, notfoundAction) => MediaClientService.GetMediaRatingByUser(id, userId, successAction, notfoundAction))"
PutRatingMethod="@((id, request) => MediaClientService.PutMediaRating(id, request))" PutRatingMethod="@((id, request) => MediaClientService.PutMediaRating(id, request))"
DeleteRatingMethod="@(id => MediaClientService.DeleteMediaRating(id))"> DeleteRatingMethod="@(id => MediaClientService.DeleteMediaRating(id))">
<MoviesFilterFormComponent/> <MoviesFilterFormComponent/>
</DatabasePageComponent> </ListComponent>
break; break;
case "series": case "series":
<DatabasePageComponent TItem="SeriesResponse" <ListComponent TItem="SeriesResponse"
TQuery="SeriesQueryParameters" TQuery="SeriesQueryParameters"
Title="TV series database" Title="TV series database"
IdSource="@(item => item.Id)" IdSource="@(item => item.Id)"
NameSource="@(item => item.Title)" NameSource="@(item => item.Title)"
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)" AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
RatingSource="@(item => item.Rating)" RatingSource="@(item => item.Rating)"
UrlIdTemplate="/media/{0}" UrlIdTemplate="/media/{0}"
PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))" PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
ItemDownloadingTask="@(SeriesClientService.GetAllSeries)" ItemDownloadingTask="@(SeriesClientService.GetAllSeries)"
SortingOptions="@(new Dictionary<string, string> SortingOptions="@(new Dictionary<string, string>
{ {
{ "rating.count", "Number of ratings" }, { "rating.count", "Number of ratings" },
{ "rating.average", "Average rating" }, { "rating.average", "Average rating" },
{ "title", "Title" }, { "title", "Title" },
{ "release_date", "Release date" }, { "release_date", "Release date" },
})" })"
PosterPlaceholder="/assets/media_poster.png" PosterPlaceholder="/assets/media_poster.png"
GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))" GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))"
GetUserRatingMethod="@((id, userId, successAction, notfoundAction) => MediaClientService.GetMediaRatingByUser(id, userId, successAction, notfoundAction))" GetUserRatingMethod="@((id, userId, successAction, notfoundAction) => MediaClientService.GetMediaRatingByUser(id, userId, successAction, notfoundAction))"
PutRatingMethod="@((id, request) => MediaClientService.PutMediaRating(id, request))" PutRatingMethod="@((id, request) => MediaClientService.PutMediaRating(id, request))"
DeleteRatingMethod="@(id => MediaClientService.DeleteMediaRating(id))"> DeleteRatingMethod="@(id => MediaClientService.DeleteMediaRating(id))">
<SeriesFilterFormComponent/> <SeriesFilterFormComponent/>
</DatabasePageComponent> </ListComponent>
break; break;
case "people": case "people":
<DatabasePageComponent TItem="PersonResponse" <ListComponent TItem="PersonResponse"
TQuery="PersonQueryParameters" TQuery="PersonQueryParameters"
Title="People database" Title="People database"
IdSource="@(item => item.Id)" IdSource="@(item => item.Id)"
NameSource="@(item => item.Name)" NameSource="@(item => item.Name)"
RatingSource="@(item => item.Rating)" RatingSource="@(item => item.Rating)"
UrlIdTemplate="/person/{0}" UrlIdTemplate="/person/{0}"
PictureDownloadingTask="@((id, action) => PersonsClientService.GetPersonPhoto(id, action))" PictureDownloadingTask="@((id, action) => PersonsClientService.GetPersonPhoto(id, action))"
ItemDownloadingTask="@(PersonsClientService.GetAllPersons)" ItemDownloadingTask="@(PersonsClientService.GetAllPersons)"
SortingOptions="@(new Dictionary<string, string> SortingOptions="@(new Dictionary<string, string>
{ {
{ "rating.count", "Number of ratings" }, { "rating.count", "Number of ratings" },
{ "rating.average", "Average rating" }, { "rating.average", "Average rating" },
{ "name", "Name" }, { "name", "Name" },
{ "birth_date", "Birth date" }, { "birth_date", "Birth date" },
{ "death_date", "Death date" }, { "death_date", "Death date" },
})" })"
PosterPlaceholder="/assets/person_poster.png" PosterPlaceholder="/assets/person_poster.png"
GetGlobalRatingMethod="@((id, action) => PersonsClientService.GetPersonGlobalRating(id, action))"> GetGlobalRatingMethod="@((id, action) => PersonsClientService.GetPersonGlobalRating(id, action))">
<PersonsFilterFormComponent/> <PersonsFilterFormComponent/>
</DatabasePageComponent> </ListComponent>
break; break;
} }

View File

@@ -1,5 +1,9 @@
@using System.Text @using System.Text
@using WatchIt.Common.Model.Movies
@using WatchIt.Common.Model.Series
@using WatchIt.Website.Components.Pages.UserPage.Panels @using WatchIt.Website.Components.Pages.UserPage.Panels
@using WatchIt.Website.Components.Common.ListComponent
@using WatchIt.Website.Components.Pages.UserPage.Subcomponents
@page "/user/{id:long?}" @page "/user/{id:long?}"
@@ -56,17 +60,59 @@
<Tab Name="movies">Movies</Tab> <Tab Name="movies">Movies</Tab>
<Tab Name="series">TV Series</Tab> <Tab Name="series">TV Series</Tab>
<Tab Name="people">People</Tab> <Tab Name="people">People</Tab>
<Tab Name="people">Roles</Tab> <Tab Name="roles">Roles</Tab>
</Items> </Items>
<Content> <Content>
<TabPanel Name="summary"> <TabPanel Name="summary">
</TabPanel> </TabPanel>
<TabPanel Name="movies"> <TabPanel Name="movies">
<div class="mt-default">
<ListComponent TItem="MovieRatedResponse"
TQuery="MovieRatedQueryParameters"
Title="Rated movies"
IdSource="@(item => item.Id)"
NameSource="@(item => item.Title)"
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
RatingSource="@(item => item.Rating)"
SecondaryRatingSource="@(item => _owner ? null : item.UserRating)"
SecondaryRatingTitle="User rating"
UrlIdTemplate="/media/{0}"
PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
ItemDownloadingTask="@((query, action) => AccountsClientService.GetAccountRatedMovies(Id!.Value, query, action))"
SortingOptions="@(new Dictionary<string, string> { { "user_rating", "User rating" }, { "rating.count", "Number of ratings" }, { "rating.average", "Average rating" }, { "title", "Title" }, { "release_date", "Release date" } })"
PosterPlaceholder="/assets/media_poster.png"
GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))"
GetUserRatingMethod="@((id, userId, successAction, notfoundAction) => MediaClientService.GetMediaRatingByUser(id, userId, successAction, notfoundAction))"
PutRatingMethod="@((id, request) => MediaClientService.PutMediaRating(id, request))"
DeleteRatingMethod="@(id => MediaClientService.DeleteMediaRating(id))">
<MoviesRatedFilterFormComponent/>
</ListComponent>
</div>
</TabPanel> </TabPanel>
<TabPanel Name="series"> <TabPanel Name="series">
<div class="mt-default">
<ListComponent TItem="SeriesRatedResponse"
TQuery="SeriesRatedQueryParameters"
Title="Rated TV series"
IdSource="@(item => item.Id)"
NameSource="@(item => item.Title)"
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
RatingSource="@(item => item.Rating)"
SecondaryRatingSource="@(item => _owner ? null : item.UserRating)"
SecondaryRatingTitle="User rating"
UrlIdTemplate="/media/{0}"
PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
ItemDownloadingTask="@((query, action) => AccountsClientService.GetAccountRatedSeries(Id!.Value, query, action))"
SortingOptions="@(new Dictionary<string, string> { { "user_rating", "User rating" }, { "rating.count", "Number of ratings" }, { "rating.average", "Average rating" }, { "title", "Title" }, { "release_date", "Release date" } })"
PosterPlaceholder="/assets/media_poster.png"
GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))"
GetUserRatingMethod="@((id, userId, successAction, notfoundAction) => MediaClientService.GetMediaRatingByUser(id, userId, successAction, notfoundAction))"
PutRatingMethod="@((id, request) => MediaClientService.PutMediaRating(id, request))"
DeleteRatingMethod="@(id => MediaClientService.DeleteMediaRating(id))">
<SeriesRatedFilterFormComponent/>
</ListComponent>
</div>
</TabPanel> </TabPanel>
<TabPanel Name="people"> <TabPanel Name="people">

View File

@@ -3,6 +3,7 @@ using WatchIt.Common.Model.Accounts;
using WatchIt.Website.Layout; using WatchIt.Website.Layout;
using WatchIt.Website.Services.Authentication; using WatchIt.Website.Services.Authentication;
using WatchIt.Website.Services.Client.Accounts; using WatchIt.Website.Services.Client.Accounts;
using WatchIt.Website.Services.Client.Media;
namespace WatchIt.Website.Pages; namespace WatchIt.Website.Pages;
@@ -13,6 +14,7 @@ public partial class UserPage : ComponentBase
[Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] private IAuthenticationService AuthenticationService { get; set; } = default!; [Inject] private IAuthenticationService AuthenticationService { get; set; } = default!;
[Inject] private IAccountsClientService AccountsClientService { get; set; } = default!; [Inject] private IAccountsClientService AccountsClientService { get; set; } = default!;
[Inject] private IMediaClientService MediaClientService { get; set; } = default!;
#endregion #endregion
@@ -78,7 +80,7 @@ public partial class UserPage : ComponentBase
Id = user.Id; Id = user.Id;
} }
await AccountsClientService.GetAccountInfoById(Id.Value, data => _accountData = data); await AccountsClientService.GetAccountInfo(Id.Value, data => _accountData = data);
_owner = Id.Value == user?.Id; _owner = Id.Value == user?.Id;
} }

View File

@@ -22,9 +22,10 @@
"AuthenticateRefresh": "/authenticate-refresh", "AuthenticateRefresh": "/authenticate-refresh",
"Logout": "/logout", "Logout": "/logout",
"GetProfilePicture": "/{0}/profile-picture", "GetProfilePicture": "/{0}/profile-picture",
"GetAccountInfoById": "/{0}/info", "GetAccountInfo": "/{0}/info",
"GetAccountInfo": "/info", "PutAccountInfo": "/info",
"PutAccountInfo": "/info" "GetAccountRatedMovies": "/{0}/movies",
"GetAccountRatedSeries": "/{0}/series"
}, },
"Genders": { "Genders": {
"Base": "/genders", "Base": "/genders",