@@ -0,0 +1,25 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Accounts;
|
||||
|
||||
public abstract class AccountProfilePicture
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("image")]
|
||||
public required byte[] Image { get; set; }
|
||||
|
||||
[JsonPropertyName("mime_type")]
|
||||
public required string MimeType { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
public AccountProfilePicture() {}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Accounts;
|
||||
|
||||
public class AccountProfilePictureResponse : AccountProfilePicture
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public required Guid Id { get; set; }
|
||||
|
||||
[JsonPropertyName("upload_date")]
|
||||
public required DateTime UploadDate { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
public AccountProfilePictureResponse() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public AccountProfilePictureResponse(Database.Model.Account.AccountProfilePicture accountProfilePicture)
|
||||
{
|
||||
Id = accountProfilePicture.Id;
|
||||
Image = accountProfilePicture.Image;
|
||||
MimeType = accountProfilePicture.MimeType;
|
||||
UploadDate = accountProfilePicture.UploadDate;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -25,7 +25,10 @@ public class RegisterResponse
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
|
||||
[JsonConstructor]
|
||||
public RegisterResponse() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public RegisterResponse(Account account)
|
||||
{
|
||||
|
||||
@@ -15,6 +15,9 @@ public class GenreResponse : Genre
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
public GenreResponse() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public GenreResponse(Database.Model.Common.Genre genre)
|
||||
|
||||
21
WatchIt.Common/WatchIt.Common.Model/Media/Media.cs
Normal file
21
WatchIt.Common/WatchIt.Common.Model/Media/Media.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public abstract class Media
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public required string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("original_title")]
|
||||
public string? OriginalTitle { get; set; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[JsonPropertyName("release_date")]
|
||||
public DateOnly? ReleaseDate { get; set; }
|
||||
|
||||
[JsonPropertyName("length")]
|
||||
public short? Length { get; set; }
|
||||
}
|
||||
@@ -24,6 +24,7 @@ public class MediaPhotoRequest : MediaPhoto
|
||||
item.MediaId = MediaId;
|
||||
item.Image = Image;
|
||||
item.MimeType = MimeType;
|
||||
item.UploadDate = DateTime.Now;
|
||||
}
|
||||
|
||||
public void UpdateMediaPhotoImageBackground(MediaPhotoImageBackground item)
|
||||
|
||||
24
WatchIt.Common/WatchIt.Common.Model/Media/MediaPoster.cs
Normal file
24
WatchIt.Common/WatchIt.Common.Model/Media/MediaPoster.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public abstract class MediaPoster
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("image")]
|
||||
public required byte[] Image { get; set; }
|
||||
|
||||
[JsonPropertyName("mime_type")]
|
||||
public required string MimeType { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public override string ToString() => $"data:{MimeType};base64,{Convert.ToBase64String(Image)}";
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public class MediaPosterRequest : MediaPoster
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MediaPosterRequest() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public MediaPosterRequest(MediaPosterResponse response)
|
||||
{
|
||||
Image = response.Image;
|
||||
MimeType = response.MimeType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public MediaPosterImage CreateMediaPosterImage() => new MediaPosterImage
|
||||
{
|
||||
Image = Image,
|
||||
MimeType = MimeType,
|
||||
};
|
||||
|
||||
public void UpdateMediaPosterImage(MediaPosterImage item)
|
||||
{
|
||||
item.Image = Image;
|
||||
item.MimeType = MimeType;
|
||||
item.UploadDate = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public class MediaPosterResponse : MediaPoster
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[JsonPropertyName("upload_date")]
|
||||
public DateTime UploadDate { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
public MediaPosterResponse() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public MediaPosterResponse(MediaPosterImage mediaPhotoImage)
|
||||
{
|
||||
Id = mediaPhotoImage.Id;
|
||||
Image = mediaPhotoImage.Image;
|
||||
MimeType = mediaPhotoImage.MimeType;
|
||||
UploadDate = mediaPhotoImage.UploadDate;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
38
WatchIt.Common/WatchIt.Common.Model/Media/MediaResponse.cs
Normal file
38
WatchIt.Common/WatchIt.Common.Model/Media/MediaResponse.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public class MediaResponse : Media
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public MediaType Type { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
public MediaResponse() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public MediaResponse(Database.Model.Media.Media media, MediaType mediaType)
|
||||
{
|
||||
Id = media.Id;
|
||||
Title = media.Title;
|
||||
OriginalTitle = media.OriginalTitle;
|
||||
Description = media.Description;
|
||||
ReleaseDate = media.ReleaseDate;
|
||||
Length = media.Length;
|
||||
Type = mediaType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
7
WatchIt.Common/WatchIt.Common.Model/Media/MediaType.cs
Normal file
7
WatchIt.Common/WatchIt.Common.Model/Media/MediaType.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public enum MediaType
|
||||
{
|
||||
Movie,
|
||||
Series
|
||||
}
|
||||
@@ -2,23 +2,8 @@
|
||||
|
||||
namespace WatchIt.Common.Model.Movies;
|
||||
|
||||
public class Movie
|
||||
public class Movie : Media.Media
|
||||
{
|
||||
[JsonPropertyName("title")]
|
||||
public required string Title { get; set; }
|
||||
|
||||
[JsonPropertyName("original_title")]
|
||||
public string? OriginalTitle { get; set; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[JsonPropertyName("release_date")]
|
||||
public DateOnly? ReleaseDate { get; set; }
|
||||
|
||||
[JsonPropertyName("length")]
|
||||
public short? Length { get; set; }
|
||||
|
||||
[JsonPropertyName("budget")]
|
||||
public decimal? Budget { get; set; }
|
||||
}
|
||||
@@ -1,9 +1,29 @@
|
||||
using WatchIt.Database.Model.Media;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Movies;
|
||||
|
||||
public class MovieRequest : Movie
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public MovieRequest(MovieResponse initData)
|
||||
{
|
||||
Title = initData.Title;
|
||||
OriginalTitle = initData.OriginalTitle;
|
||||
Description = initData.Description;
|
||||
ReleaseDate = initData.ReleaseDate;
|
||||
Length = initData.Length;
|
||||
Budget = initData.Budget;
|
||||
}
|
||||
|
||||
public MovieRequest() {}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public Database.Model.Media.Media CreateMedia() => new Database.Model.Media.Media
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Movies;
|
||||
@@ -7,6 +8,7 @@ public class MovieResponse : Movie
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
#endregion
|
||||
@@ -15,6 +17,9 @@ public class MovieResponse : Movie
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
public MovieResponse() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public MovieResponse(MediaMovie mediaMovie)
|
||||
{
|
||||
|
||||
9
WatchIt.Common/WatchIt.Common.Model/Series/Series.cs
Normal file
9
WatchIt.Common/WatchIt.Common.Model/Series/Series.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Series;
|
||||
|
||||
public class Series : Media.Media
|
||||
{
|
||||
[JsonPropertyName("has_ended")]
|
||||
public bool HasEnded { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WatchIt.Common.Query;
|
||||
|
||||
namespace WatchIt.Common.Model.Series;
|
||||
|
||||
public class SeriesQueryParameters : QueryParameters<SeriesResponse>
|
||||
{
|
||||
#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; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public override bool IsMeetingConditions(SeriesResponse item) =>
|
||||
(
|
||||
TestString(item.Title, Title)
|
||||
&&
|
||||
TestString(item.OriginalTitle, OriginalTitle)
|
||||
&&
|
||||
TestString(item.Description, Description)
|
||||
&&
|
||||
TestComparable(item.ReleaseDate, ReleaseDate, ReleaseDateFrom, ReleaseDateTo)
|
||||
&&
|
||||
TestComparable(item.Length, Length, LengthFrom, LengthTo)
|
||||
&&
|
||||
TestBoolean(item.HasEnded, HasEnded)
|
||||
);
|
||||
|
||||
#endregion
|
||||
}
|
||||
59
WatchIt.Common/WatchIt.Common.Model/Series/SeriesRequest.cs
Normal file
59
WatchIt.Common/WatchIt.Common.Model/Series/SeriesRequest.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Series;
|
||||
|
||||
public class SeriesRequest : Series
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public SeriesRequest(SeriesResponse initData)
|
||||
{
|
||||
Title = initData.Title;
|
||||
OriginalTitle = initData.OriginalTitle;
|
||||
Description = initData.Description;
|
||||
ReleaseDate = initData.ReleaseDate;
|
||||
Length = initData.Length;
|
||||
HasEnded = initData.HasEnded;
|
||||
}
|
||||
|
||||
public SeriesRequest() {}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public Database.Model.Media.Media CreateMedia() => new Database.Model.Media.Media
|
||||
{
|
||||
Title = Title,
|
||||
OriginalTitle = OriginalTitle,
|
||||
Description = Description,
|
||||
ReleaseDate = ReleaseDate,
|
||||
Length = Length,
|
||||
};
|
||||
|
||||
public MediaSeries CreateMediaSeries(long id) => new MediaSeries
|
||||
{
|
||||
Id = id,
|
||||
HasEnded = HasEnded,
|
||||
};
|
||||
|
||||
public void UpdateMedia(Database.Model.Media.Media media)
|
||||
{
|
||||
media.Title = Title;
|
||||
media.OriginalTitle = OriginalTitle;
|
||||
media.Description = Description;
|
||||
media.ReleaseDate = ReleaseDate;
|
||||
media.Length = Length;
|
||||
}
|
||||
|
||||
public void UpdateMediaSeries(MediaSeries mediaSeries)
|
||||
{
|
||||
mediaSeries.HasEnded = HasEnded;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
36
WatchIt.Common/WatchIt.Common.Model/Series/SeriesResponse.cs
Normal file
36
WatchIt.Common/WatchIt.Common.Model/Series/SeriesResponse.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Series;
|
||||
|
||||
public class SeriesResponse : Series
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
public SeriesResponse() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public SeriesResponse(MediaSeries mediaSeries)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -7,7 +7,7 @@ public class HttpResponse
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private HttpResponseMessage _message;
|
||||
private readonly HttpResponseMessage _message;
|
||||
|
||||
private Action? _2XXAction;
|
||||
private Action? _400Action;
|
||||
@@ -32,37 +32,37 @@ public class HttpResponse
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public HttpResponse RegisterActionFor2XXSuccess(Action action)
|
||||
public HttpResponse RegisterActionFor2XXSuccess(Action? action)
|
||||
{
|
||||
_2XXAction = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse RegisterActionFor2XXSuccess<T>(Action<T> action)
|
||||
public HttpResponse RegisterActionFor2XXSuccess<T>(Action<T>? action)
|
||||
{
|
||||
_2XXAction = () => Invoke(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse RegisterActionFor400BadRequest(Action<IDictionary<string, string[]>> action)
|
||||
public HttpResponse RegisterActionFor400BadRequest(Action<IDictionary<string, string[]>>? action)
|
||||
{
|
||||
_400Action = () => Invoke(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse RegisterActionFor401Unauthorized(Action action)
|
||||
public HttpResponse RegisterActionFor401Unauthorized(Action? action)
|
||||
{
|
||||
_401Action = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse RegisterActionFor403Forbidden(Action action)
|
||||
public HttpResponse RegisterActionFor403Forbidden(Action? action)
|
||||
{
|
||||
_403Action = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpResponse RegisterActionFor404NotFound(Action action)
|
||||
public HttpResponse RegisterActionFor404NotFound(Action? action)
|
||||
{
|
||||
_404Action = action;
|
||||
return this;
|
||||
@@ -86,21 +86,21 @@ public class HttpResponse
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private async void Invoke<T>(Action<T> action)
|
||||
private async void Invoke<T>(Action<T>? action)
|
||||
{
|
||||
Stream streamData = await _message.Content.ReadAsStreamAsync();
|
||||
T? data = await JsonSerializer.DeserializeAsync<T>(streamData);
|
||||
action.Invoke(data!);
|
||||
action?.Invoke(data!);
|
||||
}
|
||||
|
||||
private async void Invoke(Action<IDictionary<string, string[]>> action)
|
||||
private async void Invoke(Action<IDictionary<string, string[]>>? action)
|
||||
{
|
||||
Stream streamData = await _message.Content.ReadAsStreamAsync();
|
||||
ValidationProblemDetails? data = await JsonSerializer.DeserializeAsync<ValidationProblemDetails>(streamData, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
});
|
||||
action.Invoke(data!.Errors);
|
||||
action?.Invoke(data!.Errors);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -7,7 +7,7 @@ public class MediaPosterImage
|
||||
public Guid Id { get; set; }
|
||||
public required byte[] Image { get; set; }
|
||||
public required string MimeType { get; set; }
|
||||
public required DateTime UploadDate { get; set; }
|
||||
public DateTime UploadDate { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -29,5 +29,16 @@ public class AccountsController(IAccountsControllerService accountsControllerSer
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> AuthenticateRefresh() => await accountsControllerService.AuthenticateRefresh();
|
||||
|
||||
|
||||
[HttpDelete("logout")]
|
||||
[Authorize(AuthenticationSchemes = "refresh")]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
||||
public async Task<ActionResult> Logout() => await accountsControllerService.Logout();
|
||||
|
||||
[HttpGet("{id}/profile-picture")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(AccountProfilePictureResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetAccountProfilePicture([FromRoute(Name = "id")]long id) => await accountsControllerService.GetAccountProfilePicture(id);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WatchIt.Common.Model.Genres;
|
||||
@@ -11,33 +12,125 @@ 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<ActionResult> GetMedia([FromRoute] long id) => await mediaControllerService.GetMedia(id);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region GENRES
|
||||
|
||||
[HttpGet("{id}/genres")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(IEnumerable<GenreResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetGenres([FromRoute]long id) => await mediaControllerService.GetGenres(id);
|
||||
public async Task<ActionResult> GetMediaGenres([FromRoute]long id) => await mediaControllerService.GetMediaGenres(id);
|
||||
|
||||
[HttpPost("{id}/genres/{genre_id}")]
|
||||
[Authorize]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> PostGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.PostGenre(id, genreId);
|
||||
public async Task<ActionResult> PostMediaGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.PostMediaGenre(id, genreId);
|
||||
|
||||
[HttpDelete("{id}/genres/{genre_id}")]
|
||||
[Authorize]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> DeleteGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.DeleteGenre(id, genreId);
|
||||
public async Task<ActionResult> DeleteMediaGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.DeleteMediaGenre(id, genreId);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region RATING
|
||||
|
||||
[HttpGet("{id}/rating")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(MediaRatingResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> 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<ActionResult> 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<ActionResult> 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<ActionResult> DeleteMediaRating([FromRoute] long id) => await mediaControllerService.DeleteMediaRating(id);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region VIEW COUNT
|
||||
|
||||
[HttpPost("{id}/view")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> PostMediaView([FromRoute] long id) => await mediaControllerService.PostMediaView(id);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region POSTER
|
||||
|
||||
[HttpGet("{id}/poster")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(MediaPosterResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetMediaPoster([FromRoute] long id) => await mediaControllerService.GetMediaPoster(id);
|
||||
|
||||
[HttpPut("{id}/poster")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(MediaPosterResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> 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<ActionResult> 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<ActionResult> GetMediaRandomBackgroundPhoto([FromRoute]long id) => await mediaControllerService.GetMediaRandomBackgroundPhoto(id);
|
||||
public async Task<ActionResult> GetMediaPhotoRandomBackground([FromRoute]long id) => await mediaControllerService.GetMediaRandomBackgroundPhoto(id);
|
||||
|
||||
[HttpGet("photos/{photo_id}")]
|
||||
[AllowAnonymous]
|
||||
@@ -54,10 +147,10 @@ public class MediaController(IMediaControllerService mediaControllerService)
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetRandomBackgroundPhoto() => await mediaControllerService.GetRandomBackgroundPhoto();
|
||||
public async Task<ActionResult> GetPhotoRandomBackground() => await mediaControllerService.GetRandomBackgroundPhoto();
|
||||
|
||||
[HttpPost("photos")]
|
||||
[Authorize]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
@@ -65,7 +158,7 @@ public class MediaController(IMediaControllerService mediaControllerService)
|
||||
public async Task<ActionResult> PostPhoto([FromBody]MediaPhotoRequest body) => await mediaControllerService.PostPhoto(body);
|
||||
|
||||
[HttpPut("photos/{photo_id}")]
|
||||
[Authorize]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
@@ -74,10 +167,12 @@ public class MediaController(IMediaControllerService mediaControllerService)
|
||||
public async Task<ActionResult> PutPhoto([FromRoute(Name = "photo_id")]Guid photoId, [FromBody]MediaPhotoRequest body) => await mediaControllerService.PutPhoto(photoId, body);
|
||||
|
||||
[HttpDelete("photos/{photo_id}")]
|
||||
[Authorize]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> DeletePhoto([FromRoute(Name = "photo_id")]Guid photoId) => await mediaControllerService.DeletePhoto(photoId);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WatchIt.Common.Model.Genres;
|
||||
@@ -9,38 +11,76 @@ namespace WatchIt.WebAPI.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("movies")]
|
||||
public class MoviesController(IMoviesControllerService moviesControllerService) : ControllerBase
|
||||
public class MoviesController : ControllerBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly IMoviesControllerService _moviesControllerService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MoviesController(IMoviesControllerService moviesControllerService)
|
||||
{
|
||||
_moviesControllerService = moviesControllerService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(IEnumerable<MovieResponse>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult> GetAll(MovieQueryParameters query) => await moviesControllerService.GetAll(query);
|
||||
public async Task<ActionResult> GetAllMovies(MovieQueryParameters query) => await _moviesControllerService.GetAllMovies(query);
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(MovieResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> Get([FromRoute]long id) => await moviesControllerService.Get(id);
|
||||
public async Task<ActionResult> GetMovie([FromRoute] long id) => await _moviesControllerService.GetMovie(id);
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(MovieResponse), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> Post([FromBody]MovieRequest body) => await moviesControllerService.Post(body);
|
||||
public async Task<ActionResult> PostMovie([FromBody] MovieRequest body) => await _moviesControllerService.PostMovie(body);
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> Put([FromRoute]long id, [FromBody]MovieRequest body) => await moviesControllerService.Put(id, body);
|
||||
public async Task<ActionResult> PutMovie([FromRoute] long id, [FromBody]MovieRequest body) => await _moviesControllerService.PutMovie(id, body);
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> Delete([FromRoute] long id) => await moviesControllerService.Delete(id);
|
||||
public async Task<ActionResult> DeleteMovie([FromRoute] long id) => await _moviesControllerService.DeleteMovie(id);
|
||||
|
||||
#endregion
|
||||
|
||||
#region View count
|
||||
|
||||
[HttpGet("view")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(IEnumerable<MovieResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult> GetMoviesViewRank([FromQuery] int first = 5, [FromQuery] int days = 7) => await _moviesControllerService.GetMoviesViewRank(first, days);
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Database;
|
||||
using WatchIt.WebAPI.Services.Controllers.Series;
|
||||
|
||||
namespace WatchIt.WebAPI.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("series")]
|
||||
public class SeriesController : ControllerBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly ISeriesControllerService _seriesControllerService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public SeriesController(ISeriesControllerService seriesControllerService)
|
||||
{
|
||||
_seriesControllerService = seriesControllerService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(IEnumerable<SeriesResponse>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult> GetAllSeries(SeriesQueryParameters query) => await _seriesControllerService.GetAllSeries(query);
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(SeriesResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetSeries([FromRoute] long id) => await _seriesControllerService.GetSeries(id);
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(SeriesResponse), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> PostSeries([FromBody] SeriesRequest body) => await _seriesControllerService.PostSeries(body);
|
||||
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> PutSeries([FromRoute] long id, [FromBody]SeriesRequest body) => await _seriesControllerService.PutSeries(id, body);
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> DeleteSeries([FromRoute] long id) => await _seriesControllerService.DeleteSeries(id);
|
||||
|
||||
#endregion
|
||||
|
||||
#region View count
|
||||
|
||||
[HttpGet("view")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(IEnumerable<SeriesResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult> GetSeriesViewRank([FromQuery] int first = 5, [FromQuery] int days = 7) => await _seriesControllerService.GetSeriesViewRank(first, days);
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -16,6 +17,7 @@
|
||||
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Genres\WatchIt.WebAPI.Services.Controllers.Genres.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Media\WatchIt.WebAPI.Services.Controllers.Media.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Movies\WatchIt.WebAPI.Services.Controllers.Movies.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Series\WatchIt.WebAPI.Services.Controllers.Series.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -11,6 +11,7 @@ using WatchIt.WebAPI.Services.Controllers.Common;
|
||||
using WatchIt.WebAPI.Services.Utility.Tokens;
|
||||
using WatchIt.WebAPI.Services.Utility.Tokens.Exceptions;
|
||||
using WatchIt.WebAPI.Services.Utility.User;
|
||||
using AccountProfilePicture = WatchIt.Common.Model.Accounts.AccountProfilePicture;
|
||||
|
||||
namespace WatchIt.WebAPI.Services.Controllers.Accounts;
|
||||
|
||||
@@ -73,16 +74,10 @@ public class AccountsControllerService(
|
||||
return RequestResult.Unauthorized();
|
||||
}
|
||||
|
||||
AuthenticateResponse response;
|
||||
string refreshToken;
|
||||
try
|
||||
{
|
||||
Task<string> refreshTokenTask = tokensService.ExtendRefreshTokenAsync(token.Account, token.Id);
|
||||
Task<string> accessTokenTask = tokensService.CreateAccessTokenAsync(token.Account);
|
||||
response = new AuthenticateResponse
|
||||
{
|
||||
AccessToken = await accessTokenTask,
|
||||
RefreshToken = await refreshTokenTask,
|
||||
};
|
||||
refreshToken = await tokensService.ExtendRefreshTokenAsync(token.Account, token.Id);
|
||||
}
|
||||
catch (TokenNotFoundException)
|
||||
{
|
||||
@@ -90,11 +85,48 @@ public class AccountsControllerService(
|
||||
}
|
||||
catch (TokenNotExtendableException)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
refreshToken = userService.GetRawToken().Replace("Bearer ", string.Empty);
|
||||
}
|
||||
|
||||
string accessToken = await tokensService.CreateAccessTokenAsync(token.Account);
|
||||
|
||||
logger.LogInformation($"Account with ID {token.AccountId} was authenticated by token refreshing");
|
||||
return RequestResult.Ok(response);
|
||||
return RequestResult.Ok(new AuthenticateResponse
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
RefreshToken = refreshToken,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<RequestResult> Logout()
|
||||
{
|
||||
Guid jti = userService.GetJti();
|
||||
AccountRefreshToken? token = await database.AccountRefreshTokens.FirstOrDefaultAsync(x => x.Id == jti);
|
||||
if (token is not null)
|
||||
{
|
||||
database.AccountRefreshTokens.Attach(token);
|
||||
database.AccountRefreshTokens.Remove(token);
|
||||
await database.SaveChangesAsync();
|
||||
}
|
||||
return RequestResult.NoContent();
|
||||
}
|
||||
|
||||
public async Task<RequestResult> GetAccountProfilePicture(long id)
|
||||
{
|
||||
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (account is null)
|
||||
{
|
||||
return RequestResult.BadRequest()
|
||||
.AddValidationError("id", "Account with this id does not exists");
|
||||
}
|
||||
|
||||
if (account.ProfilePicture is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
AccountProfilePictureResponse picture = new AccountProfilePictureResponse(account.ProfilePicture);
|
||||
return RequestResult.Ok(picture);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -8,4 +8,6 @@ public interface IAccountsControllerService
|
||||
Task<RequestResult> Register(RegisterRequest data);
|
||||
Task<RequestResult> Authenticate(AuthenticateRequest data);
|
||||
Task<RequestResult> AuthenticateRefresh();
|
||||
Task<RequestResult> Logout();
|
||||
Task<RequestResult> GetAccountProfilePicture(long id);
|
||||
}
|
||||
@@ -5,9 +5,23 @@ namespace WatchIt.WebAPI.Services.Controllers.Media;
|
||||
|
||||
public interface IMediaControllerService
|
||||
{
|
||||
Task<RequestResult> GetGenres(long mediaId);
|
||||
Task<RequestResult> PostGenre(long mediaId, short genreId);
|
||||
Task<RequestResult> DeleteGenre(long mediaId, short genreId);
|
||||
Task<RequestResult> GetMedia(long mediaId);
|
||||
|
||||
Task<RequestResult> GetMediaGenres(long mediaId);
|
||||
Task<RequestResult> PostMediaGenre(long mediaId, short genreId);
|
||||
Task<RequestResult> DeleteMediaGenre(long mediaId, short genreId);
|
||||
|
||||
Task<RequestResult> GetMediaRating(long mediaId);
|
||||
Task<RequestResult> GetMediaRatingByUser(long mediaId, long userId);
|
||||
Task<RequestResult> PutMediaRating(long mediaId, MediaRatingRequest data);
|
||||
Task<RequestResult> DeleteMediaRating(long mediaId);
|
||||
|
||||
Task<RequestResult> PostMediaView(long mediaId);
|
||||
|
||||
Task<RequestResult> GetMediaPoster(long mediaId);
|
||||
Task<RequestResult> PutMediaPoster(long mediaId, MediaPosterRequest data);
|
||||
Task<RequestResult> DeleteMediaPoster(long mediaId);
|
||||
|
||||
Task<RequestResult> GetPhoto(Guid id);
|
||||
Task<RequestResult> GetPhotos(MediaPhotoQueryParameters query);
|
||||
Task<RequestResult> GetRandomBackgroundPhoto();
|
||||
|
||||
@@ -4,6 +4,8 @@ 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.Database.Model.ViewCount;
|
||||
using WatchIt.WebAPI.Services.Controllers.Common;
|
||||
using WatchIt.WebAPI.Services.Utility.User;
|
||||
|
||||
@@ -13,19 +15,40 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
|
||||
{
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<RequestResult> GetGenres(long mediaId)
|
||||
#region Main
|
||||
|
||||
public async Task<RequestResult> GetMedia(long mediaId)
|
||||
{
|
||||
MediaMovie? item = await database.MediaMovies.FirstOrDefaultAsync(x => x.Id == mediaId);
|
||||
Database.Model.Media.Media? item = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
IEnumerable<GenreResponse> genres = item.Media.MediaGenres.Select(x => new GenreResponse(x.Genre));
|
||||
MediaMovie? movie = await database.MediaMovies.FirstOrDefaultAsync(x => x.Id == mediaId);
|
||||
|
||||
MediaResponse mediaResponse = new MediaResponse(item, movie is not null ? MediaType.Movie : MediaType.Series);
|
||||
|
||||
return RequestResult.Ok(mediaResponse);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Genres
|
||||
|
||||
public async Task<RequestResult> GetMediaGenres(long mediaId)
|
||||
{
|
||||
Database.Model.Media.Media? item = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
IEnumerable<GenreResponse> genres = item.MediaGenres.Select(x => new GenreResponse(x.Genre));
|
||||
return RequestResult.Ok(genres);
|
||||
}
|
||||
|
||||
public async Task<RequestResult> PostGenre(long mediaId, short genreId)
|
||||
public async Task<RequestResult> PostMediaGenre(long mediaId, short genreId)
|
||||
{
|
||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
@@ -50,7 +73,7 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
|
||||
return RequestResult.Ok();
|
||||
}
|
||||
|
||||
public async Task<RequestResult> DeleteGenre(long mediaId, short genreId)
|
||||
public async Task<RequestResult> DeleteMediaGenre(long mediaId, short genreId)
|
||||
{
|
||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
@@ -71,6 +94,207 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
|
||||
return RequestResult.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rating
|
||||
|
||||
public async Task<RequestResult> 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<RequestResult> 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<RequestResult> 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<RequestResult> 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 View count
|
||||
|
||||
public async Task<RequestResult> PostMediaView(long mediaId)
|
||||
{
|
||||
Database.Model.Media.Media? item = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
DateOnly dateNow = DateOnly.FromDateTime(DateTime.Now);
|
||||
ViewCountMedia? viewCount = await database.ViewCountsMedia.FirstOrDefaultAsync(x => x.MediaId == mediaId && x.Date == dateNow);
|
||||
if (viewCount is null)
|
||||
{
|
||||
viewCount = new ViewCountMedia
|
||||
{
|
||||
MediaId = mediaId,
|
||||
Date = dateNow,
|
||||
ViewCount = 1
|
||||
};
|
||||
await database.ViewCountsMedia.AddAsync(viewCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
viewCount.ViewCount++;
|
||||
}
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Poster
|
||||
|
||||
public async Task<RequestResult> GetMediaPoster(long mediaId)
|
||||
{
|
||||
Database.Model.Media.Media? media = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId);
|
||||
if (media is null)
|
||||
{
|
||||
return RequestResult.BadRequest();
|
||||
}
|
||||
|
||||
MediaPosterImage? poster = media.MediaPosterImage;
|
||||
if (poster is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
MediaPosterResponse data = new MediaPosterResponse(poster);
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
public async Task<RequestResult> PutMediaPoster(long mediaId, 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 == mediaId);
|
||||
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();
|
||||
|
||||
MediaPosterResponse returnData = new MediaPosterResponse(media.MediaPosterImage);
|
||||
return RequestResult.Ok(returnData);
|
||||
}
|
||||
|
||||
public async Task<RequestResult> DeleteMediaPoster(long mediaId)
|
||||
{
|
||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
Database.Model.Media.Media? media = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId);
|
||||
|
||||
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<RequestResult> GetPhoto(Guid id)
|
||||
{
|
||||
MediaPhotoImage? item = await database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == id);
|
||||
@@ -199,4 +423,6 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -5,9 +5,11 @@ namespace WatchIt.WebAPI.Services.Controllers.Movies;
|
||||
|
||||
public interface IMoviesControllerService
|
||||
{
|
||||
Task<RequestResult> GetAll(MovieQueryParameters query);
|
||||
Task<RequestResult> Get(long id);
|
||||
Task<RequestResult> Post(MovieRequest data);
|
||||
Task<RequestResult> Put(long id, MovieRequest data);
|
||||
Task<RequestResult> Delete(long id);
|
||||
Task<RequestResult> GetAllMovies(MovieQueryParameters query);
|
||||
Task<RequestResult> GetMovie(long id);
|
||||
Task<RequestResult> PostMovie(MovieRequest data);
|
||||
Task<RequestResult> PutMovie(long id, MovieRequest data);
|
||||
Task<RequestResult> DeleteMovie(long id);
|
||||
|
||||
Task<RequestResult> GetMoviesViewRank(int first, int days);
|
||||
}
|
||||
@@ -7,20 +7,45 @@ using WatchIt.WebAPI.Services.Utility.User;
|
||||
|
||||
namespace WatchIt.WebAPI.Services.Controllers.Movies;
|
||||
|
||||
public class MoviesControllerService(DatabaseContext database, IUserService userService) : IMoviesControllerService
|
||||
public class MoviesControllerService : IMoviesControllerService
|
||||
{
|
||||
#region PUBLIC METHODS
|
||||
#region SERVICES
|
||||
|
||||
public async Task<RequestResult> GetAll(MovieQueryParameters query)
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
private readonly IUserService _userService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MoviesControllerService(DatabaseContext database, IUserService userService)
|
||||
{
|
||||
IEnumerable<MovieResponse> data = await database.MediaMovies.Select(x => new MovieResponse(x)).ToListAsync();
|
||||
_database = database;
|
||||
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
public async Task<RequestResult> GetAllMovies(MovieQueryParameters query)
|
||||
{
|
||||
IEnumerable<MovieResponse> data = await _database.MediaMovies.Select(x => new MovieResponse(x)).ToListAsync();
|
||||
data = query.PrepareData(data);
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
public async Task<RequestResult> Get(long id)
|
||||
public async Task<RequestResult> GetMovie(long id)
|
||||
{
|
||||
MediaMovie? item = await database.MediaMovies.FirstOrDefaultAsync(x => x.Id == id);
|
||||
MediaMovie? item = await _database.MediaMovies.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
@@ -30,33 +55,33 @@ public class MoviesControllerService(DatabaseContext database, IUserService user
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
public async Task<RequestResult> Post(MovieRequest data)
|
||||
public async Task<RequestResult> PostMovie(MovieRequest data)
|
||||
{
|
||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||
UserValidator validator = _userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
Media mediaItem = data.CreateMedia();
|
||||
await database.Media.AddAsync(mediaItem);
|
||||
await database.SaveChangesAsync();
|
||||
await _database.Media.AddAsync(mediaItem);
|
||||
await _database.SaveChangesAsync();
|
||||
MediaMovie mediaMovieItem = data.CreateMediaMovie(mediaItem.Id);
|
||||
await database.MediaMovies.AddAsync(mediaMovieItem);
|
||||
await database.SaveChangesAsync();
|
||||
await _database.MediaMovies.AddAsync(mediaMovieItem);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Created($"movies/{mediaItem.Id}", new MovieResponse(mediaMovieItem));
|
||||
}
|
||||
|
||||
public async Task<RequestResult> Put(long id, MovieRequest data)
|
||||
public async Task<RequestResult> PutMovie(long id, MovieRequest data)
|
||||
{
|
||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||
UserValidator validator = _userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaMovie? item = await database.MediaMovies.FirstOrDefaultAsync(x => x.Id == id);
|
||||
MediaMovie? item = await _database.MediaMovies.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
@@ -64,49 +89,74 @@ public class MoviesControllerService(DatabaseContext database, IUserService user
|
||||
|
||||
data.UpdateMediaMovie(item);
|
||||
data.UpdateMedia(item.Media);
|
||||
await database.SaveChangesAsync();
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Ok();
|
||||
}
|
||||
|
||||
public async Task<RequestResult> Delete(long id)
|
||||
public async Task<RequestResult> DeleteMovie(long id)
|
||||
{
|
||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||
UserValidator validator = _userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaMovie? item = await database.MediaMovies.FirstOrDefaultAsync(x => x.Id == id);
|
||||
MediaMovie? item = await _database.MediaMovies.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
database.MediaMovies.Attach(item);
|
||||
database.MediaMovies.Remove(item);
|
||||
database.MediaPosterImages.Attach(item.Media.MediaPosterImage!);
|
||||
database.MediaPosterImages.Remove(item.Media.MediaPosterImage!);
|
||||
database.MediaPhotoImages.AttachRange(item.Media.MediaPhotoImages);
|
||||
database.MediaPhotoImages.RemoveRange(item.Media.MediaPhotoImages);
|
||||
database.MediaGenres.AttachRange(item.Media.MediaGenres);
|
||||
database.MediaGenres.RemoveRange(item.Media.MediaGenres);
|
||||
database.MediaProductionCountries.AttachRange(item.Media.MediaProductionCountries);
|
||||
database.MediaProductionCountries.RemoveRange(item.Media.MediaProductionCountries);
|
||||
database.PersonActorRoles.AttachRange(item.Media.PersonActorRoles);
|
||||
database.PersonActorRoles.RemoveRange(item.Media.PersonActorRoles);
|
||||
database.PersonCreatorRoles.AttachRange(item.Media.PersonCreatorRoles);
|
||||
database.PersonCreatorRoles.RemoveRange(item.Media.PersonCreatorRoles);
|
||||
database.RatingsMedia.AttachRange(item.Media.RatingMedia);
|
||||
database.RatingsMedia.RemoveRange(item.Media.RatingMedia);
|
||||
database.ViewCountsMedia.AttachRange(item.Media.ViewCountsMedia);
|
||||
database.ViewCountsMedia.RemoveRange(item.Media.ViewCountsMedia);
|
||||
database.Media.Attach(item.Media);
|
||||
database.Media.Remove(item.Media);
|
||||
await database.SaveChangesAsync();
|
||||
_database.MediaMovies.Attach(item);
|
||||
_database.MediaMovies.Remove(item);
|
||||
_database.MediaPosterImages.Attach(item.Media.MediaPosterImage!);
|
||||
_database.MediaPosterImages.Remove(item.Media.MediaPosterImage!);
|
||||
_database.MediaPhotoImages.AttachRange(item.Media.MediaPhotoImages);
|
||||
_database.MediaPhotoImages.RemoveRange(item.Media.MediaPhotoImages);
|
||||
_database.MediaGenres.AttachRange(item.Media.MediaGenres);
|
||||
_database.MediaGenres.RemoveRange(item.Media.MediaGenres);
|
||||
_database.MediaProductionCountries.AttachRange(item.Media.MediaProductionCountries);
|
||||
_database.MediaProductionCountries.RemoveRange(item.Media.MediaProductionCountries);
|
||||
_database.PersonActorRoles.AttachRange(item.Media.PersonActorRoles);
|
||||
_database.PersonActorRoles.RemoveRange(item.Media.PersonActorRoles);
|
||||
_database.PersonCreatorRoles.AttachRange(item.Media.PersonCreatorRoles);
|
||||
_database.PersonCreatorRoles.RemoveRange(item.Media.PersonCreatorRoles);
|
||||
_database.RatingsMedia.AttachRange(item.Media.RatingMedia);
|
||||
_database.RatingsMedia.RemoveRange(item.Media.RatingMedia);
|
||||
_database.ViewCountsMedia.AttachRange(item.Media.ViewCountsMedia);
|
||||
_database.ViewCountsMedia.RemoveRange(item.Media.ViewCountsMedia);
|
||||
_database.Media.Attach(item.Media);
|
||||
_database.Media.Remove(item.Media);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Ok();
|
||||
return RequestResult.NoContent();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region View count
|
||||
|
||||
public async Task<RequestResult> GetMoviesViewRank(int first, int days)
|
||||
{
|
||||
if (first < 1 || days < 1)
|
||||
{
|
||||
return RequestResult.BadRequest();
|
||||
}
|
||||
|
||||
DateOnly startDate = DateOnly.FromDateTime(DateTime.Now).AddDays(-days);
|
||||
IEnumerable<MediaMovie> rawData = await _database.MediaMovies.OrderByDescending(x => x.Media.ViewCountsMedia.Where(y => y.Date >= startDate)
|
||||
.Sum(y => y.ViewCount))
|
||||
.ThenBy(x => x.Id)
|
||||
.Take(first)
|
||||
.ToListAsync();
|
||||
|
||||
IEnumerable<MovieResponse> data = rawData.Select(x => new MovieResponse(x));
|
||||
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.WebAPI.Services.Controllers.Common;
|
||||
|
||||
namespace WatchIt.WebAPI.Services.Controllers.Series;
|
||||
|
||||
public interface ISeriesControllerService
|
||||
{
|
||||
Task<RequestResult> GetAllSeries(SeriesQueryParameters query);
|
||||
Task<RequestResult> GetSeries(long id);
|
||||
Task<RequestResult> PostSeries(SeriesRequest data);
|
||||
Task<RequestResult> PutSeries(long id, SeriesRequest data);
|
||||
Task<RequestResult> DeleteSeries(long id);
|
||||
|
||||
Task<RequestResult> GetSeriesViewRank(int first, int days);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Database;
|
||||
using WatchIt.Database.Model.Media;
|
||||
using WatchIt.WebAPI.Services.Controllers.Common;
|
||||
using WatchIt.WebAPI.Services.Utility.User;
|
||||
|
||||
namespace WatchIt.WebAPI.Services.Controllers.Series;
|
||||
|
||||
public class SeriesControllerService : ISeriesControllerService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
private readonly IUserService _userService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public SeriesControllerService(DatabaseContext database, IUserService userService)
|
||||
{
|
||||
_database = database;
|
||||
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
public async Task<RequestResult> GetAllSeries(SeriesQueryParameters query)
|
||||
{
|
||||
IEnumerable<SeriesResponse> data = await _database.MediaSeries.Select(x => new SeriesResponse(x)).ToListAsync();
|
||||
data = query.PrepareData(data);
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
public async Task<RequestResult> GetSeries(long id)
|
||||
{
|
||||
MediaSeries? item = await _database.MediaSeries.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
SeriesResponse data = new SeriesResponse(item);
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
public async Task<RequestResult> PostSeries(SeriesRequest data)
|
||||
{
|
||||
UserValidator validator = _userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
Media mediaItem = data.CreateMedia();
|
||||
await _database.Media.AddAsync(mediaItem);
|
||||
await _database.SaveChangesAsync();
|
||||
MediaSeries mediaSeriesItem = data.CreateMediaSeries(mediaItem.Id);
|
||||
await _database.MediaSeries.AddAsync(mediaSeriesItem);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Created($"series/{mediaItem.Id}", new SeriesResponse(mediaSeriesItem));
|
||||
}
|
||||
|
||||
public async Task<RequestResult> PutSeries(long id, SeriesRequest data)
|
||||
{
|
||||
UserValidator validator = _userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaSeries? item = await _database.MediaSeries.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
data.UpdateMediaSeries(item);
|
||||
data.UpdateMedia(item.Media);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Ok();
|
||||
}
|
||||
|
||||
public async Task<RequestResult> DeleteSeries(long id)
|
||||
{
|
||||
UserValidator validator = _userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaSeries? item = await _database.MediaSeries.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
_database.MediaSeries.Attach(item);
|
||||
_database.MediaSeries.Remove(item);
|
||||
_database.MediaPosterImages.Attach(item.Media.MediaPosterImage!);
|
||||
_database.MediaPosterImages.Remove(item.Media.MediaPosterImage!);
|
||||
_database.MediaPhotoImages.AttachRange(item.Media.MediaPhotoImages);
|
||||
_database.MediaPhotoImages.RemoveRange(item.Media.MediaPhotoImages);
|
||||
_database.MediaGenres.AttachRange(item.Media.MediaGenres);
|
||||
_database.MediaGenres.RemoveRange(item.Media.MediaGenres);
|
||||
_database.MediaProductionCountries.AttachRange(item.Media.MediaProductionCountries);
|
||||
_database.MediaProductionCountries.RemoveRange(item.Media.MediaProductionCountries);
|
||||
_database.PersonActorRoles.AttachRange(item.Media.PersonActorRoles);
|
||||
_database.PersonActorRoles.RemoveRange(item.Media.PersonActorRoles);
|
||||
_database.PersonCreatorRoles.AttachRange(item.Media.PersonCreatorRoles);
|
||||
_database.PersonCreatorRoles.RemoveRange(item.Media.PersonCreatorRoles);
|
||||
_database.RatingsMedia.AttachRange(item.Media.RatingMedia);
|
||||
_database.RatingsMedia.RemoveRange(item.Media.RatingMedia);
|
||||
_database.ViewCountsMedia.AttachRange(item.Media.ViewCountsMedia);
|
||||
_database.ViewCountsMedia.RemoveRange(item.Media.ViewCountsMedia);
|
||||
_database.Media.Attach(item.Media);
|
||||
_database.Media.Remove(item.Media);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.NoContent();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region View count
|
||||
|
||||
public async Task<RequestResult> GetSeriesViewRank(int first, int days)
|
||||
{
|
||||
if (first < 1 || days < 1)
|
||||
{
|
||||
return RequestResult.BadRequest();
|
||||
}
|
||||
|
||||
DateOnly startDate = DateOnly.FromDateTime(DateTime.Now).AddDays(-days);
|
||||
IEnumerable<MediaSeries> rawData = await _database.MediaSeries.OrderByDescending(x => x.Media.ViewCountsMedia.Where(y => y.Date >= startDate)
|
||||
.Sum(y => y.ViewCount))
|
||||
.ThenBy(x => x.Id)
|
||||
.Take(first)
|
||||
.ToListAsync();
|
||||
|
||||
IEnumerable<SeriesResponse> data = rawData.Select(x => new SeriesResponse(x));
|
||||
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.Database\WatchIt.Database\WatchIt.Database.csproj" />
|
||||
<ProjectReference Include="..\..\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.User\WatchIt.WebAPI.Services.Utility.User.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.WebAPI.Services.Controllers.Common\WatchIt.WebAPI.Services.Controllers.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -85,21 +85,24 @@ public class TokensService(DatabaseContext database, IConfigurationService confi
|
||||
return TokenToString(tokenDescriptor);
|
||||
}
|
||||
|
||||
protected SecurityTokenDescriptor CreateBaseSecurityTokenDescriptor(Account account, Guid id, DateTime expirationTime) => new SecurityTokenDescriptor
|
||||
protected SecurityTokenDescriptor CreateBaseSecurityTokenDescriptor(Account account, Guid id, DateTime expirationTime)
|
||||
{
|
||||
Subject = new ClaimsIdentity(new List<Claim>
|
||||
return new SecurityTokenDescriptor
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Jti, id.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Sub, account.Id.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Email, account.Email),
|
||||
new Claim(JwtRegisteredClaimNames.UniqueName, account.Username),
|
||||
new Claim(JwtRegisteredClaimNames.Exp, expirationTime.Ticks.ToString()),
|
||||
new Claim("admin", account.IsAdmin.ToString()),
|
||||
}),
|
||||
Expires = expirationTime,
|
||||
Issuer = configurationService.Data.Authentication.Issuer,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurationService.Data.Authentication.Key)), SecurityAlgorithms.HmacSha512)
|
||||
};
|
||||
Subject = new ClaimsIdentity(new List<Claim>
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Jti, id.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Sub, account.Id.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Email, account.Email),
|
||||
new Claim(JwtRegisteredClaimNames.UniqueName, account.Username),
|
||||
new Claim(JwtRegisteredClaimNames.Exp, expirationTime.Ticks.ToString()),
|
||||
new Claim("admin", account.IsAdmin.ToString()),
|
||||
}),
|
||||
Expires = expirationTime,
|
||||
Issuer = configurationService.Data.Authentication.Issuer,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurationService.Data.Authentication.Key)), SecurityAlgorithms.HmacSha512)
|
||||
};
|
||||
}
|
||||
|
||||
protected string TokenToString(SecurityTokenDescriptor tokenDescriptor)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace WatchIt.WebAPI.Services.Utility.User;
|
||||
|
||||
public interface IUserService
|
||||
{
|
||||
ClaimsPrincipal GetRawUser();
|
||||
string? GetRawToken();
|
||||
UserValidator GetValidator();
|
||||
Guid GetJti();
|
||||
long GetUserId();
|
||||
}
|
||||
@@ -18,6 +18,15 @@ public class UserService(DatabaseContext database, IHttpContextAccessor accessor
|
||||
return accessor.HttpContext.User;
|
||||
}
|
||||
|
||||
public string? GetRawToken()
|
||||
{
|
||||
if (accessor.HttpContext is null)
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
return accessor.HttpContext.Request.Headers.Authorization;
|
||||
}
|
||||
|
||||
public UserValidator GetValidator()
|
||||
{
|
||||
ClaimsPrincipal rawUser = GetRawUser();
|
||||
@@ -31,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
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Security.Claims;
|
||||
using WatchIt.Database;
|
||||
|
||||
namespace WatchIt.WebAPI.Services.Utility.User;
|
||||
|
||||
public class UserValidator
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
protected readonly DatabaseContext _database;
|
||||
protected readonly ClaimsPrincipal _claimsPrincipal;
|
||||
protected readonly List<string> _validationErrors;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public bool IsValid { get; protected set; }
|
||||
public IEnumerable<string> ValidationErrors => _validationErrors;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
internal UserValidator(DatabaseContext database, ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
_database = database;
|
||||
_claimsPrincipal = claimsPrincipal;
|
||||
_validationErrors = new List<string>();
|
||||
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public UserValidator MustBeAdmin()
|
||||
{
|
||||
Claim adminClaim = _claimsPrincipal.FindFirst(x => x.Type == "admin")!;
|
||||
if (adminClaim.Value == bool.FalseString)
|
||||
{
|
||||
IsValid = false;
|
||||
_validationErrors.Add("User is not admin");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentValidation;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Database;
|
||||
|
||||
namespace WatchIt.WebAPI.Validators.Media;
|
||||
|
||||
public class MediaPosterRequestValidator : AbstractValidator<MediaPosterRequest>
|
||||
{
|
||||
public MediaPosterRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Image).NotEmpty();
|
||||
RuleFor(x => x.MimeType).Matches(@"\w+/.+").WithMessage("Incorrect mimetype");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentValidation;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
|
||||
namespace WatchIt.WebAPI.Validators.Movies;
|
||||
|
||||
public class MovieRequestValidator : AbstractValidator<MovieRequest>
|
||||
{
|
||||
public MovieRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Title).NotEmpty().MaximumLength(250);
|
||||
RuleFor(x => x.OriginalTitle).MaximumLength(250);
|
||||
RuleFor(x => x.Description).MaximumLength(1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentValidation;
|
||||
using WatchIt.Common.Model.Series;
|
||||
|
||||
namespace WatchIt.WebAPI.Validators.Movies;
|
||||
|
||||
public class SeriesRequestValidator : AbstractValidator<SeriesRequest>
|
||||
{
|
||||
public SeriesRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Title).NotEmpty().MaximumLength(250);
|
||||
RuleFor(x => x.OriginalTitle).MaximumLength(250);
|
||||
RuleFor(x => x.Description).MaximumLength(1000);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using WatchIt.Database;
|
||||
@@ -5,13 +6,36 @@ using WatchIt.Database.Model.Account;
|
||||
|
||||
namespace WatchIt.WebAPI.WorkerServices;
|
||||
|
||||
public class DeleteExpiredRefreshTokensService(ILogger<DeleteExpiredRefreshTokensService> logger, DatabaseContext database) : BackgroundService
|
||||
public class DeleteExpiredRefreshTokensService : BackgroundService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly ILogger<DeleteExpiredRefreshTokensService> _logger;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public DeleteExpiredRefreshTokensService(ILogger<DeleteExpiredRefreshTokensService> logger, IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
|
||||
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
|
||||
|
||||
Task delayTask = Task.Delay(300000, stoppingToken);
|
||||
Task actionTask = Action();
|
||||
@@ -22,9 +46,16 @@ public class DeleteExpiredRefreshTokensService(ILogger<DeleteExpiredRefreshToken
|
||||
|
||||
protected async Task Action()
|
||||
{
|
||||
IEnumerable<AccountRefreshToken> tokens = database.AccountRefreshTokens.Where(x => x.ExpirationDate < DateTime.UtcNow);
|
||||
database.AccountRefreshTokens.AttachRange(tokens);
|
||||
database.AccountRefreshTokens.RemoveRange(tokens);
|
||||
await database.SaveChangesAsync();
|
||||
using (IServiceScope scope = _serviceScopeFactory.CreateScope())
|
||||
{
|
||||
DatabaseContext database = scope.ServiceProvider.GetService<DatabaseContext>();
|
||||
|
||||
IEnumerable<AccountRefreshToken> tokens = database.AccountRefreshTokens.Where(x => x.ExpirationDate < DateTime.UtcNow);
|
||||
database.AccountRefreshTokens.AttachRange(tokens);
|
||||
database.AccountRefreshTokens.RemoveRange(tokens);
|
||||
await database.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using FluentValidation;
|
||||
@@ -5,12 +6,14 @@ 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;
|
||||
using WatchIt.WebAPI.Services.Controllers.Genres;
|
||||
using WatchIt.WebAPI.Services.Controllers.Media;
|
||||
using WatchIt.WebAPI.Services.Controllers.Movies;
|
||||
using WatchIt.WebAPI.Services.Controllers.Series;
|
||||
using WatchIt.WebAPI.Services.Utility.Configuration;
|
||||
using WatchIt.WebAPI.Services.Utility.Tokens;
|
||||
using WatchIt.WebAPI.Services.Utility.User;
|
||||
@@ -26,12 +29,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 +43,8 @@ public static class Program
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
@@ -56,6 +60,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;
|
||||
@@ -121,7 +128,7 @@ public static class Program
|
||||
|
||||
private static WebApplicationBuilder SetupDatabase(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddDbContext<DatabaseContext>(x => x.UseLazyLoadingProxies().UseNpgsql(builder.Configuration.GetConnectionString("Default")), ServiceLifetime.Singleton);
|
||||
builder.Services.AddDbContext<DatabaseContext>(x => x.UseLazyLoadingProxies().UseNpgsql(builder.Configuration.GetConnectionString("Default")), ServiceLifetime.Transient);
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -134,15 +141,16 @@ public static class Program
|
||||
private static WebApplicationBuilder SetupServices(this WebApplicationBuilder builder)
|
||||
{
|
||||
// Utility
|
||||
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
|
||||
builder.Services.AddSingleton<ITokensService, TokensService>();
|
||||
builder.Services.AddSingleton<IUserService, UserService>();
|
||||
builder.Services.AddTransient<IConfigurationService, ConfigurationService>();
|
||||
builder.Services.AddTransient<ITokensService, TokensService>();
|
||||
builder.Services.AddTransient<IUserService, UserService>();
|
||||
|
||||
// Controller
|
||||
builder.Services.AddSingleton<IAccountsControllerService, AccountsControllerService>();
|
||||
builder.Services.AddSingleton<IGenresControllerService, GenresControllerService>();
|
||||
builder.Services.AddSingleton<IMoviesControllerService, MoviesControllerService>();
|
||||
builder.Services.AddSingleton<IMediaControllerService, MediaControllerService>();
|
||||
builder.Services.AddTransient<IAccountsControllerService, AccountsControllerService>();
|
||||
builder.Services.AddTransient<IGenresControllerService, GenresControllerService>();
|
||||
builder.Services.AddTransient<IMoviesControllerService, MoviesControllerService>();
|
||||
builder.Services.AddTransient<IMediaControllerService, MediaControllerService>();
|
||||
builder.Services.AddTransient<ISeriesControllerService, SeriesControllerService>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Net.Http.Headers;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||
|
||||
namespace WatchIt.Website.Services.Utility.Authentication;
|
||||
|
||||
public class AuthenticationService : IAuthenticationService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly AuthenticationStateProvider _authenticationStateProvider;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ITokensService _tokensService;
|
||||
private readonly IAccountsWebAPIService _accountsWebAPIService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AuthenticationService(AuthenticationStateProvider authenticationStateProvider, HttpClient httpClient, ITokensService tokensService, IAccountsWebAPIService accountsWebAPIService)
|
||||
{
|
||||
_authenticationStateProvider = authenticationStateProvider;
|
||||
_httpClient = httpClient;
|
||||
_tokensService = tokensService;
|
||||
_accountsWebAPIService = accountsWebAPIService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<User?> GetUserAsync()
|
||||
{
|
||||
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||
|
||||
if (!GetAuthenticationStatusAsync(state))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new User
|
||||
{
|
||||
Id = int.Parse(state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.Sub)!.Value),
|
||||
Username = state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.UniqueName)!.Value,
|
||||
Email = state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.Email)!.Value,
|
||||
IsAdmin = bool.Parse(state.User.FindFirst(x => x.Type == "admin")!.Value),
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> GetAuthenticationStatusAsync()
|
||||
{
|
||||
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||
return GetAuthenticationStatusAsync(state);
|
||||
}
|
||||
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
string? refreshToken = await _tokensService.GetRefreshToken();
|
||||
if (refreshToken is not null)
|
||||
{
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("refresh", refreshToken.Replace("\"", ""));
|
||||
await _accountsWebAPIService.Logout();
|
||||
_httpClient.DefaultRequestHeaders.Authorization = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private bool GetAuthenticationStatusAsync(AuthenticationState state)
|
||||
{
|
||||
return state.User.HasClaim(x => x.Type == JwtRegisteredClaimNames.Iss && x.Value == "WatchIt");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace WatchIt.Website.Services.Utility.Authentication;
|
||||
|
||||
public interface IAuthenticationService
|
||||
{
|
||||
Task<User?> GetUserAsync();
|
||||
Task<bool> GetAuthenticationStatusAsync();
|
||||
Task LogoutAsync();
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using WatchIt.Common.Model.Accounts;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||
|
||||
namespace WatchIt.Website.Services.Utility.Authentication;
|
||||
|
||||
public class JWTAuthenticationStateProvider : AuthenticationStateProvider
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
private readonly ILogger<JWTAuthenticationStateProvider> _logger;
|
||||
|
||||
private readonly ITokensService _tokensService;
|
||||
private readonly IAccountsWebAPIService _accountsService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public JWTAuthenticationStateProvider(HttpClient httpClient, ILogger<JWTAuthenticationStateProvider> logger, ITokensService tokensService, IAccountsWebAPIService accountsService)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
|
||||
_logger = logger;
|
||||
|
||||
_tokensService = tokensService;
|
||||
_accountsService = accountsService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||
{
|
||||
AuthenticationState state = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
|
||||
|
||||
Task<string?> accessTokenTask = _tokensService.GetAccessToken();
|
||||
Task<string?> refreshTokenTask = _tokensService.GetRefreshToken();
|
||||
await Task.WhenAll(accessTokenTask, refreshTokenTask);
|
||||
string? accessToken = await accessTokenTask;
|
||||
string? refreshToken = await refreshTokenTask;
|
||||
|
||||
bool refreshed = false;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(accessToken))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(refreshToken))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
string? accessTokenNew = await Refresh(refreshToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(accessToken))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
accessToken = accessTokenNew;
|
||||
refreshed = true;
|
||||
}
|
||||
|
||||
IEnumerable<Claim> claims = GetClaimsFromToken(accessToken);
|
||||
|
||||
Claim? expClaim = claims.FirstOrDefault(c => c.Type == "exp");
|
||||
|
||||
if (expClaim is not null && ConvertFromUnixTimestamp(int.Parse(expClaim.Value)) > DateTime.UtcNow)
|
||||
{
|
||||
if (refreshed)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(refreshToken))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
string? accessTokenNew = await Refresh(refreshToken);
|
||||
|
||||
if (accessTokenNew is null)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
accessToken = accessTokenNew;
|
||||
}
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("\"", ""));
|
||||
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private async Task<string?> Refresh(string refreshToken)
|
||||
{
|
||||
AuthenticateResponse? response = null;
|
||||
|
||||
void SetResponse(AuthenticateResponse data)
|
||||
{
|
||||
response = data;
|
||||
}
|
||||
|
||||
await _accountsService.AuthenticateRefresh(SetResponse);
|
||||
|
||||
if (response is not null)
|
||||
{
|
||||
await _tokensService.SaveAuthenticationData(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _tokensService.RemoveAuthenticationData();
|
||||
}
|
||||
|
||||
return response?.AccessToken;
|
||||
}
|
||||
|
||||
private static IEnumerable<Claim> GetClaimsFromToken(string token)
|
||||
{
|
||||
string payload = token.Split('.')[1];
|
||||
|
||||
switch (payload.Length % 4)
|
||||
{
|
||||
case 2: payload += "=="; break;
|
||||
case 3: payload += "="; break;
|
||||
}
|
||||
|
||||
byte[] jsonBytes = Convert.FromBase64String(payload);
|
||||
Dictionary<string, object>? keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
|
||||
|
||||
if (keyValuePairs is null)
|
||||
{
|
||||
throw new Exception("Incorrect token");
|
||||
}
|
||||
|
||||
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
|
||||
}
|
||||
|
||||
public static DateTime ConvertFromUnixTimestamp(int timestamp)
|
||||
{
|
||||
DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0);
|
||||
date = date.AddSeconds(timestamp);
|
||||
return date;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace WatchIt.Website.Services.Utility.Authentication;
|
||||
|
||||
public class User
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public required long Id { get; init; }
|
||||
public required string Username { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public required bool IsAdmin { get; init; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Accounts\WatchIt.Website.Services.WebAPI.Accounts.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services.Utility.Tokens\WatchIt.Website.Services.Utility.Tokens.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -6,4 +6,6 @@ public class Accounts
|
||||
public string Register { get; set; }
|
||||
public string Authenticate { get; set; }
|
||||
public string AuthenticateRefresh { get; set; }
|
||||
public string Logout { get; set; }
|
||||
public string GetProfilePicture { get; set; }
|
||||
}
|
||||
@@ -4,5 +4,6 @@ public class ConfigurationData
|
||||
{
|
||||
public Logging Logging { get; set; }
|
||||
public string AllowedHosts { get; set; }
|
||||
public StorageKeys StorageKeys { get; set; }
|
||||
public Endpoints Endpoints { get; set; }
|
||||
}
|
||||
@@ -5,6 +5,7 @@ public class Endpoints
|
||||
public string Base { get; set; }
|
||||
public Accounts Accounts { get; set; }
|
||||
public Genres Genres { get; set; }
|
||||
public Movies Movies { get; set; }
|
||||
public Media Media { get; set; }
|
||||
public Movies Movies { get; set; }
|
||||
public Series Series { get; set; }
|
||||
}
|
||||
@@ -3,10 +3,19 @@
|
||||
public class Media
|
||||
{
|
||||
public string Base { get; set; }
|
||||
public string Get { get; set; }
|
||||
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 PostMediaView { get; set; }
|
||||
public string GetPhotoMediaRandomBackground { get; set; }
|
||||
public string GetPoster { get; set; }
|
||||
public string PutPoster { get; set; }
|
||||
public string DeletePoster { get; set; }
|
||||
public string GetPhoto { get; set; }
|
||||
public string GetPhotos { get; set; }
|
||||
public string GetPhotoRandomBackground { get; set; }
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
public class Movies
|
||||
{
|
||||
public string Base { get; set; }
|
||||
public string GetAll { get; set; }
|
||||
public string Get { get; set; }
|
||||
public string Post { get; set; }
|
||||
public string Put { get; set; }
|
||||
public string Delete { get; set; }
|
||||
public string GetGenres { get; set; }
|
||||
public string PostGenre { get; set; }
|
||||
public string DeleteGenre { get; set; }
|
||||
public string GetAllMovies { get; set; }
|
||||
public string GetMovie { get; set; }
|
||||
public string PostMovie { get; set; }
|
||||
public string PutMovie { get; set; }
|
||||
public string DeleteMovie { get; set; }
|
||||
public string GetMoviesViewRank { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace WatchIt.Website.Services.Utility.Configuration.Model;
|
||||
|
||||
public class Series
|
||||
{
|
||||
public string Base { get; set; }
|
||||
public string GetAllSeries { get; set; }
|
||||
public string GetSeries { get; set; }
|
||||
public string PostSeries { get; set; }
|
||||
public string PutSeries { get; set; }
|
||||
public string DeleteSeries { get; set; }
|
||||
public string GetSeriesViewRank { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace WatchIt.Website.Services.Utility.Configuration.Model;
|
||||
|
||||
public class StorageKeys
|
||||
{
|
||||
public string AccessToken { get; set; }
|
||||
public string RefreshToken { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using WatchIt.Common.Model.Accounts;
|
||||
|
||||
namespace WatchIt.Website.Services.Utility.Tokens;
|
||||
|
||||
public interface ITokensService
|
||||
{
|
||||
Task<string?> GetAccessToken();
|
||||
Task<string?> GetRefreshToken();
|
||||
Task SaveAuthenticationData(AuthenticateResponse authenticateResponse);
|
||||
Task SaveAccessToken(string accessToken);
|
||||
Task SaveRefreshToken(string refreshToken);
|
||||
Task RemoveAuthenticationData();
|
||||
Task RemoveAccessToken();
|
||||
Task RemoveRefreshToken();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
|
||||
using WatchIt.Common.Model.Accounts;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
|
||||
namespace WatchIt.Website.Services.Utility.Tokens;
|
||||
|
||||
public class TokensService : ITokensService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly ProtectedLocalStorage _localStorageService;
|
||||
private readonly IConfigurationService _configurationService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public TokensService(ProtectedLocalStorage localStorageService, IConfigurationService configurationService)
|
||||
{
|
||||
_localStorageService = localStorageService;
|
||||
_configurationService = configurationService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<string?> GetAccessToken() => await GetValueAsync<string>(GetAccessTokenStorageKey());
|
||||
|
||||
public async Task<string?> GetRefreshToken() => await GetValueAsync<string>(GetRefreshTokenStorageKey());
|
||||
|
||||
public async Task SaveAuthenticationData(AuthenticateResponse authenticateResponse) => await Task.WhenAll(SaveAccessToken(authenticateResponse.AccessToken), SaveRefreshToken(authenticateResponse.RefreshToken));
|
||||
|
||||
public async Task SaveAccessToken(string accessToken) => await _localStorageService.SetAsync(GetAccessTokenStorageKey(), accessToken);
|
||||
|
||||
public async Task SaveRefreshToken(string refreshToken) => await _localStorageService.SetAsync(GetRefreshTokenStorageKey(), refreshToken);
|
||||
|
||||
public async Task RemoveAuthenticationData() => await Task.WhenAll(RemoveAccessToken(), RemoveRefreshToken());
|
||||
|
||||
public async Task RemoveAccessToken() => await _localStorageService.DeleteAsync(GetAccessTokenStorageKey());
|
||||
|
||||
public async Task RemoveRefreshToken() => await _localStorageService.DeleteAsync(GetRefreshTokenStorageKey());
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private string GetAccessTokenStorageKey() => _configurationService.Data.StorageKeys.AccessToken;
|
||||
private string GetRefreshTokenStorageKey() => _configurationService.Data.StorageKeys.RefreshToken;
|
||||
|
||||
private async Task<T?> GetValueAsync<T>(string key)
|
||||
{
|
||||
ProtectedBrowserStorageResult<T> result = await _localStorageService.GetAsync<T>(key);
|
||||
return result.Success ? result.Value : default;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>WatchIt.Website.Services.Utility.Token</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -2,15 +2,16 @@
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.Utility.Configuration.Model;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Accounts;
|
||||
|
||||
public class AccountsWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : BaseWebAPIService(configurationService), IAccountsWebAPIService
|
||||
public class AccountsWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService, ITokensService tokensService) : BaseWebAPIService(configurationService), IAccountsWebAPIService
|
||||
{
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task Register(RegisterRequest data, Action<RegisterResponse> createdAction, Action<IDictionary<string, string[]>> badRequestAction)
|
||||
public async Task Register(RegisterRequest data, Action<RegisterResponse>? createdAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.Register);
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
|
||||
@@ -24,7 +25,7 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse> successAction, Action<IDictionary<string, string[]>> badRequestAction, Action unauthorizedAction)
|
||||
public async Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.Authenticate);
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
|
||||
@@ -39,10 +40,13 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task AuthenticateRefresh(Action<AuthenticateResponse> successAction, Action unauthorizedAction, Action forbiddenAction)
|
||||
public async Task AuthenticateRefresh(Action<AuthenticateResponse>? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.AuthenticateRefresh);
|
||||
string? token = await tokensService.GetRefreshToken();
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||
request.Headers.Add("Authorization", $"Bearer {token}");
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
@@ -50,6 +54,31 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task Logout(Action? successAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.Logout);
|
||||
string? token = await tokensService.GetRefreshToken();
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
request.Headers.Add("Authorization", $"Bearer {token}");
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.GetProfilePicture, id);
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ namespace WatchIt.Website.Services.WebAPI.Accounts;
|
||||
|
||||
public interface IAccountsWebAPIService
|
||||
{
|
||||
Task Register(RegisterRequest data, Action<RegisterResponse> createdAction, Action<IDictionary<string, string[]>> badRequestAction);
|
||||
Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse> successAction, Action<IDictionary<string, string[]>> badRequestAction, Action unauthorizedAction);
|
||||
Task AuthenticateRefresh(Action<AuthenticateResponse> successAction, Action unauthorizedAction, Action forbiddenAction);
|
||||
Task Register(RegisterRequest data, Action<RegisterResponse>? createdAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null);
|
||||
Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null);
|
||||
Task AuthenticateRefresh(Action<AuthenticateResponse>? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task Logout(Action? successAction = null);
|
||||
Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
public enum AuthorizationType
|
||||
{
|
||||
Access,
|
||||
Refresh
|
||||
}
|
||||
@@ -1,13 +1,33 @@
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.Utility.Configuration.Model;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
public abstract class BaseWebAPIService(IConfigurationService configurationService)
|
||||
public abstract class BaseWebAPIService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
protected readonly IConfigurationService _configurationService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
protected Endpoints EndpointsConfiguration => configurationService.Data.Endpoints;
|
||||
protected Endpoints EndpointsConfiguration => _configurationService.Data.Endpoints;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
protected BaseWebAPIService(IConfigurationService configurationService)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Configuration\WatchIt.WebAPI.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Tokens\WatchIt.Website.Services.Utility.Tokens.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -5,7 +5,21 @@ namespace WatchIt.Website.Services.WebAPI.Media;
|
||||
|
||||
public interface IMediaWebAPIService
|
||||
{
|
||||
Task GetGenres(long mediaId, Action<IEnumerable<GenreResponse>> successAction, Action notFoundAction);
|
||||
Task PostGenre(long mediaId, long genreId, Action successAction, Action unauthorizedAction, Action forbiddenAction, Action notFoundAction);
|
||||
Task GetPhotoRandomBackground(Action<MediaPhotoResponse> successAction, Action notFoundAction);
|
||||
Task GetMedia(long mediaId, Action<MediaResponse>? successAction = null, Action? notFoundAction = null);
|
||||
|
||||
Task GetMediaGenres(long mediaId, Action<IEnumerable<GenreResponse>>? 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<MediaRatingResponse>? 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 DeleteMediaRating(long mediaId, Action? successAction = null, Action? unauthorizedAction = null);
|
||||
|
||||
Task PostMediaView(long mediaId, Action? successAction = null, Action? notFoundAction = null);
|
||||
|
||||
Task GetPhotoMediaRandomBackground(long mediaId, Action<MediaPhotoResponse>? successAction = null, Action? notFoundAction = null);
|
||||
Task GetPhotoRandomBackground(Action<MediaPhotoResponse>? successAction = null, Action? notFoundAction = null);
|
||||
Task GetPoster(long mediaId, Action<MediaPosterResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
|
||||
Task PutPoster(long mediaId, MediaPosterRequest data, Action<MediaPosterResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task DeletePoster(long mediaId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
}
|
||||
@@ -11,7 +11,25 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
{
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task GetGenres(long mediaId, Action<IEnumerable<GenreResponse>>? successAction = null, Action? notFoundAction = null)
|
||||
#region Main
|
||||
|
||||
public async Task GetMedia(long mediaId, Action<MediaResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.Get, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Genres
|
||||
|
||||
public async Task GetMediaGenres(long mediaId, Action<IEnumerable<GenreResponse>>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetGenres, mediaId);
|
||||
|
||||
@@ -23,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);
|
||||
|
||||
@@ -37,6 +55,95 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rating
|
||||
|
||||
public async Task GetMediaRating(long mediaId, Action<MediaRatingResponse>? 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<short>? 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<IDictionary<string, string[]>>? 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
|
||||
|
||||
#region View count
|
||||
|
||||
public async Task PostMediaView(long mediaId, Action? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.PostMediaView, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
public async Task GetPhotoMediaRandomBackground(long mediaId, Action<MediaPhotoResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetPhotoMediaRandomBackground, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task GetPhotoRandomBackground(Action<MediaPhotoResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetPhotoRandomBackground);
|
||||
@@ -45,8 +152,51 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task GetPoster(long mediaId, Action<MediaPosterResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetPoster, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task PutPoster(long mediaId, MediaPosterRequest data, Action<MediaPosterResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.PutPoster, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Put, url)
|
||||
{
|
||||
Body = data
|
||||
};
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task DeletePoster(long mediaId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.DeletePoster, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using WatchIt.Common.Model.Movies;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Movies;
|
||||
|
||||
public interface IMoviesWebAPIService
|
||||
{
|
||||
Task GetAllMovies(MovieQueryParameters? query = null, Action<IEnumerable<MovieResponse>>? successAction = null);
|
||||
Task PostMovie(MovieRequest data, Action<MovieResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task GetMovie(long id, Action<MovieResponse>? successAction = null, Action? notFoundAction = null);
|
||||
Task PutMovie(long id, MovieRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task DeleteMovie(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
|
||||
Task GetMoviesViewRank(int? first = null, int? days = null, Action<IEnumerable<MovieResponse>>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Movies;
|
||||
|
||||
public class MoviesWebAPIService : BaseWebAPIService, IMoviesWebAPIService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private IHttpClientService _httpClientService;
|
||||
private IConfigurationService _configurationService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MoviesWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : base(configurationService)
|
||||
{
|
||||
_httpClientService = httpClientService;
|
||||
_configurationService = configurationService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
public async Task GetAllMovies(MovieQueryParameters? query = null, Action<IEnumerable<MovieResponse>>? successAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.GetAllMovies);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
request.Query = query;
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task GetMovie(long id, Action<MovieResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.GetMovie, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task PostMovie(MovieRequest data, Action<MovieResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.PostMovie);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||
request.Body = data;
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task PutMovie(long id, MovieRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.PutMovie, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Put, url);
|
||||
request.Body = data;
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task DeleteMovie(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.DeleteMovie, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region View count
|
||||
|
||||
public async Task GetMoviesViewRank(int? first = null, int? days = null, Action<IEnumerable<MovieResponse>>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.GetMoviesViewRank);
|
||||
if (first.HasValue || days.HasValue)
|
||||
{
|
||||
StringBuilder urlBuilder = new StringBuilder(url);
|
||||
urlBuilder.Append('?');
|
||||
bool firstParameter = true;
|
||||
if (first.HasValue)
|
||||
{
|
||||
urlBuilder.Append($"first={first.Value}");
|
||||
firstParameter = false;
|
||||
}
|
||||
if (days.HasValue)
|
||||
{
|
||||
if (!firstParameter)
|
||||
{
|
||||
urlBuilder.Append('&');
|
||||
}
|
||||
urlBuilder.Append($"days={days.Value}");
|
||||
}
|
||||
url = urlBuilder.ToString();
|
||||
}
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override string GetServiceBase() => EndpointsConfiguration.Movies.Base;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -6,4 +6,18 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Components">
|
||||
<HintPath>..\..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.2\Microsoft.AspNetCore.Components.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Configuration\WatchIt.WebAPI.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services.WebAPI.Common\WatchIt.Website.Services.WebAPI.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using WatchIt.Common.Model.Series;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Series;
|
||||
|
||||
public interface ISeriesWebAPIService
|
||||
{
|
||||
Task GetAllSeries(SeriesQueryParameters? query = null, Action<IEnumerable<SeriesResponse>>? successAction = null);
|
||||
Task GetSeries(long id, Action<SeriesResponse>? successAction = null, Action? notFoundAction = null);
|
||||
Task PostSeries(SeriesRequest data, Action<SeriesResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task PutSeries(long id, SeriesRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task DeleteSeries(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
|
||||
Task GetSeriesViewRank(int? first = null, int? days = null, Action<IEnumerable<SeriesResponse>>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using System.Text;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Series;
|
||||
|
||||
public class SeriesWebAPIService : BaseWebAPIService, ISeriesWebAPIService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private IHttpClientService _httpClientService;
|
||||
private IConfigurationService _configurationService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public SeriesWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : base(configurationService)
|
||||
{
|
||||
_httpClientService = httpClientService;
|
||||
_configurationService = configurationService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
public async Task GetAllSeries(SeriesQueryParameters? query = null, Action<IEnumerable<SeriesResponse>>? successAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Series.GetAllSeries);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
request.Query = query;
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task GetSeries(long id, Action<SeriesResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Series.GetSeries, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task PostSeries(SeriesRequest data, Action<SeriesResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Series.PostSeries);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||
request.Body = data;
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task PutSeries(long id, SeriesRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Series.PutSeries, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Put, url);
|
||||
request.Body = data;
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task DeleteSeries(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Series.DeleteSeries, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region View count
|
||||
|
||||
public async Task GetSeriesViewRank(int? first = null, int? days = null, Action<IEnumerable<SeriesResponse>>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Series.GetSeriesViewRank);
|
||||
if (first.HasValue || days.HasValue)
|
||||
{
|
||||
StringBuilder urlBuilder = new StringBuilder(url);
|
||||
urlBuilder.Append('?');
|
||||
bool firstParameter = true;
|
||||
if (first.HasValue)
|
||||
{
|
||||
urlBuilder.Append($"first={first.Value}");
|
||||
firstParameter = false;
|
||||
}
|
||||
if (days.HasValue)
|
||||
{
|
||||
if (!firstParameter)
|
||||
{
|
||||
urlBuilder.Append('&');
|
||||
}
|
||||
urlBuilder.Append($"days={days.Value}");
|
||||
}
|
||||
url = urlBuilder.ToString();
|
||||
}
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override string GetServiceBase() => EndpointsConfiguration.Series.Base;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services.WebAPI.Common\WatchIt.Website.Services.WebAPI.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,20 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<base href="/"/>
|
||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="app.css?version=0.2"/>
|
||||
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.2"/>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="app.css?version=0.1.0.16"/>
|
||||
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.1.0.26"/>
|
||||
|
||||
<!-- BOOTSTRAP -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
|
||||
<!-- FONTS -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Belanosima:wght@400;600;700&family=PT+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
||||
|
||||
<HeadOutlet @rendermode="InteractiveServer"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes @rendermode="InteractiveServer"/>
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<Routes @rendermode="InteractiveServer"/>
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-4">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="text-danger icon-size">⚠︎</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-center">
|
||||
<h3 class="text-danger">An error occured while loading a page</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(ErrorMessage))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-center">
|
||||
<p>@ErrorMessage</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace WatchIt.Website.Components;
|
||||
|
||||
public partial class ErrorComponent : ComponentBase
|
||||
{
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter] public string? ErrorMessage { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/* CLASSES */
|
||||
|
||||
.icon-size {
|
||||
font-size: 80px;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex flex-column m-5">
|
||||
<div class="d-flex justify-content-center">
|
||||
<div id="spinner" class="spinner-border text-dark"></div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<p id="text" class="text-dark">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
/* IDS */
|
||||
|
||||
#spinner {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
#text {
|
||||
font-size: 25px;
|
||||
border-width: 0.4rem;
|
||||
}
|
||||
@@ -1,48 +1,75 @@
|
||||
@using System.Diagnostics
|
||||
@using System.Text
|
||||
@using WatchIt.Common.Model.Media
|
||||
@using WatchIt.Website.Services.WebAPI.Media
|
||||
@inherits LayoutComponentBase
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@if (loaded)
|
||||
@if (_loaded)
|
||||
{
|
||||
<div class="container-xl">
|
||||
<div class="row align-items-center m-2 rounded-3 header panel">
|
||||
<div class="col-sm-4">
|
||||
<div class="row align-items-center m-1 my-2 mb-3 rounded-3 header panel panel-header z-3">
|
||||
<div class="col-2">
|
||||
<a class="logo" href="/">
|
||||
WatchIt
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="col">
|
||||
<p>Menu</p>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="col-auto">
|
||||
<div class="d-flex flex-row-reverse">
|
||||
@if (signedIn)
|
||||
@if (_user is null)
|
||||
{
|
||||
<p>test</p>
|
||||
<a class="main-button" href="/auth">Sign in</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="main-button" href="/auth">Sign in or up</a>
|
||||
<div class="dropdown z-3">
|
||||
<a class="dropdown-toggle align-items-center text-decoration-none d-flex" id="dropdownUser" aria-expanded="false" @onclick="() => _userMenuIsActive = !_userMenuIsActive">
|
||||
<img class="rounded-circle" alt="avatar" height="30" src="@(_userProfilePicture)"/>
|
||||
<div class="text-decoration-none mx-2 text-white">@(_user.Username)</div>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-right text-small z-3" id="user-menu" aria-labelledby="dropdownUser">
|
||||
<li>
|
||||
@if (_user.IsAdmin)
|
||||
{
|
||||
<a class="dropdown-item" href="/admin">Administrator panel</a>
|
||||
}
|
||||
<div class="dropdown-menu-separator"></div>
|
||||
<a class="dropdown-item text-danger" @onclick="UserMenuLogOut">Log out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row body-content">
|
||||
<div class="col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col z-0 p-1">
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: url('@background') no-repeat center center fixed;
|
||||
background-image: url('@_background');
|
||||
|
||||
height: 100%;
|
||||
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
.logo, .main-button {
|
||||
background-image: linear-gradient(45deg, @firstGradientColor, @secondGradientColor);
|
||||
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
||||
}
|
||||
|
||||
#user-menu {
|
||||
display: @(_userMenuIsActive ? "block" : "none");
|
||||
position: fixed;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@@ -55,6 +82,10 @@
|
||||
#region SERVICES
|
||||
|
||||
[Inject] public ILogger<MainLayout> Logger { get; set; } = default!;
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] public ITokensService TokensService { get; set; } = default!;
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
[Inject] public IAccountsWebAPIService AccountsWebAPIService { get; set; } = default!;
|
||||
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
@@ -63,12 +94,15 @@
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool loaded = false;
|
||||
private bool _loaded = false;
|
||||
|
||||
private string background = "assets/background_temp.jpg";
|
||||
private string firstGradientColor = "#c6721c";
|
||||
private string secondGradientColor = "#85200c";
|
||||
private bool signedIn = false;
|
||||
private string _background = "assets/background_temp.jpg";
|
||||
private string _firstGradientColor = "#c6721c";
|
||||
private string _secondGradientColor = "#85200c";
|
||||
|
||||
private User? _user = null;
|
||||
private string _userProfilePicture = "assets/user_placeholder.png";
|
||||
private bool _userMenuIsActive = false;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -76,7 +110,29 @@
|
||||
|
||||
#region METHODS
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
List<Task> bgTasks = new List<Task>();
|
||||
|
||||
bgTasks.Add(GetBackground());
|
||||
|
||||
await GetAuthenticatedUser();
|
||||
|
||||
if (_user is not null)
|
||||
{
|
||||
bgTasks.Add(GetProfilePicture());
|
||||
}
|
||||
|
||||
await Task.WhenAll(bgTasks);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetBackground()
|
||||
{
|
||||
Action<MediaPhotoResponse> backgroundSuccess = (data) =>
|
||||
{
|
||||
@@ -86,13 +142,33 @@
|
||||
string secondColor = BitConverter.ToString(data.Background.SecondGradientColor)
|
||||
.Replace("-", string.Empty);
|
||||
|
||||
background = $"data:{data.MimeType};base64,{imageBase64}";
|
||||
firstGradientColor = $"#{firstColor}";
|
||||
secondGradientColor = $"#{secondColor}";
|
||||
_background = $"data:{data.MimeType};base64,{imageBase64}";
|
||||
_firstGradientColor = $"#{firstColor}";
|
||||
_secondGradientColor = $"#{secondColor}";
|
||||
};
|
||||
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess, null);
|
||||
|
||||
loaded = true;
|
||||
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess);
|
||||
}
|
||||
|
||||
private async Task GetAuthenticatedUser()
|
||||
{
|
||||
_user = await AuthenticationService.GetUserAsync();
|
||||
}
|
||||
|
||||
private async Task GetProfilePicture()
|
||||
{
|
||||
Action<AccountProfilePictureResponse> successAction = (data) =>
|
||||
{
|
||||
string imageBase64 = Convert.ToBase64String(data.Image);
|
||||
_userProfilePicture = $"data:{data.MimeType};base64,{imageBase64}";
|
||||
};
|
||||
await AccountsWebAPIService.GetAccountProfilePicture(_user.Id, successAction);
|
||||
}
|
||||
|
||||
private async Task UserMenuLogOut()
|
||||
{
|
||||
await AuthenticationService.LogoutAsync();
|
||||
await TokensService.RemoveAuthenticationData();
|
||||
NavigationManager.Refresh(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
36
WatchIt.Website/WatchIt.Website/Pages/AdminPage.razor
Normal file
36
WatchIt.Website/WatchIt.Website/Pages/AdminPage.razor
Normal file
@@ -0,0 +1,36 @@
|
||||
@page "/admin"
|
||||
|
||||
<PageTitle>WatchIt administrator panel</PageTitle>
|
||||
|
||||
<div class="container-fluid">
|
||||
@if (_loaded)
|
||||
{
|
||||
if (_authenticated)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col rounded-3 panel panel-regular m-1">
|
||||
<h2>Add new data</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="col rounded-3 panel panel-regular m-1" href="/media/new/movies">
|
||||
<p class="text-center text-decorations-none">New movie</p>
|
||||
</a>
|
||||
<div class="col rounded-3 panel panel-regular m-1">
|
||||
<p class="text-center">New TV series</p>
|
||||
</div>
|
||||
<div class="col rounded-3 panel panel-regular m-1">
|
||||
<p class="text-center">New TV series</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ErrorComponent ErrorMessage="You do not have permission to view this site"/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<LoadingComponent/>
|
||||
}
|
||||
</div>
|
||||
42
WatchIt.Website/WatchIt.Website/Pages/AdminPage.razor.cs
Normal file
42
WatchIt.Website/WatchIt.Website/Pages/AdminPage.razor.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
|
||||
namespace WatchIt.Website.Pages;
|
||||
|
||||
public partial class AdminPage
|
||||
{
|
||||
#region SERVICE
|
||||
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded = false;
|
||||
private bool _authenticated = false;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
User? user = await AuthenticationService.GetUserAsync();
|
||||
if (user is not null && user.IsAdmin)
|
||||
{
|
||||
_authenticated = true;
|
||||
}
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
@page "/auth"
|
||||
@layout EmptyLayout
|
||||
|
||||
<PageTitle>WatchIt - @(_authType == AuthType.SignIn ? "Sign in" : "Sign up")</PageTitle>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="h-100 d-flex align-items-center justify-content-center">
|
||||
<div class="d-inline-flex flex-column justify-content-center panel rounded-3">
|
||||
<a class="logo" href="/">
|
||||
WatchIt
|
||||
</a>
|
||||
@if (_authType == AuthType.SignIn)
|
||||
{
|
||||
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: url('@(_background)') no-repeat center center fixed;
|
||||
}
|
||||
|
||||
.logo {
|
||||
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
#region ENUMS
|
||||
|
||||
private enum AuthType
|
||||
{
|
||||
SignIn,
|
||||
SignUp
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private AuthType _authType = AuthType.SignIn;
|
||||
private string _background = "assets/background_temp.jpg";
|
||||
private string _firstGradientColor = "#c6721c";
|
||||
private string _secondGradientColor = "#85200c";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
{
|
||||
return base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
body {
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 60px;
|
||||
margin: 10px;
|
||||
}
|
||||
117
WatchIt.Website/WatchIt.Website/Pages/AuthPage.razor
Normal file
117
WatchIt.Website/WatchIt.Website/Pages/AuthPage.razor
Normal file
@@ -0,0 +1,117 @@
|
||||
@page "/auth"
|
||||
@layout EmptyLayout
|
||||
|
||||
<PageTitle>WatchIt - @(_authType == AuthType.SignIn ? "Sign in" : "Sign up")</PageTitle>
|
||||
|
||||
|
||||
|
||||
|
||||
@if (_loaded)
|
||||
{
|
||||
<div class="h-100 d-flex align-items-center justify-content-center">
|
||||
<div class="d-inline-flex flex-column justify-content-center panel panel-header rounded-3">
|
||||
<a class="logo" href="/">
|
||||
WatchIt
|
||||
</a>
|
||||
<div>
|
||||
@if (_authType == AuthType.SignIn)
|
||||
{
|
||||
<form method="post" @onsubmit="Login" @formname="login">
|
||||
<AntiforgeryToken/>
|
||||
<div>
|
||||
<label>
|
||||
Username or email:
|
||||
<InputText @bind-Value="_loginModel!.UsernameOrEmail"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Password:
|
||||
<InputText type="password" @bind-Value="_loginModel!.Password"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<InputCheckbox @bind-Value="_loginModel!.RememberMe"></InputCheckbox>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" @onsubmit="Register" @formname="register">
|
||||
<AntiforgeryToken/>
|
||||
<div>
|
||||
<label>
|
||||
Username:
|
||||
<InputText @bind-Value="_registerModel!.Username"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Email:
|
||||
<InputText @bind-Value="_registerModel!.Email"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Password:
|
||||
<InputText type="password" @bind-Value="_registerModel!.Password"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Confirm password:
|
||||
<InputText type="password" @bind-Value="_passwordConfirmation"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Sign up</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
@if (_errors is not null)
|
||||
{
|
||||
<div class="text-danger">
|
||||
@foreach (string error in _errors)
|
||||
{
|
||||
@error
|
||||
<br/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<label>
|
||||
<input type="radio" checked="@(() => _authType == AuthType.SignIn)" name="auth" @onchange="@(() => _authType = AuthType.SignIn)" />
|
||||
Sign in
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" checked="@(() => _authType == AuthType.SignUp)" name="auth" @onchange="@(() => _authType = AuthType.SignUp)" />
|
||||
Sign up
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
height: 100%;
|
||||
|
||||
background-image: url('@_background');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.logo {
|
||||
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
||||
}
|
||||
</style>
|
||||
}
|
||||
140
WatchIt.Website/WatchIt.Website/Pages/AuthPage.razor.cs
Normal file
140
WatchIt.Website/WatchIt.Website/Pages/AuthPage.razor.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model.Accounts;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
|
||||
namespace WatchIt.Website.Pages;
|
||||
|
||||
public partial class AuthPage
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
[Inject] public ILogger<AuthPage> Logger { get; set; } = default!;
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
[Inject] public ITokensService TokensService { get; set; } = default!;
|
||||
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
[Inject] public IAccountsWebAPIService AccountsWebAPIService { get; set; } = default!;
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region ENUMS
|
||||
|
||||
private enum AuthType
|
||||
{
|
||||
SignIn,
|
||||
SignUp
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded = false;
|
||||
|
||||
private AuthType _authType = AuthType.SignIn;
|
||||
private string _background = "assets/background_temp.jpg";
|
||||
private string _firstGradientColor = "#c6721c";
|
||||
private string _secondGradientColor = "#85200c";
|
||||
|
||||
private AuthenticateRequest _loginModel = new AuthenticateRequest
|
||||
{
|
||||
UsernameOrEmail = null,
|
||||
Password = null
|
||||
};
|
||||
|
||||
private RegisterRequest _registerModel = new RegisterRequest
|
||||
{
|
||||
Username = null,
|
||||
Email = null,
|
||||
Password = null
|
||||
};
|
||||
private string _passwordConfirmation;
|
||||
|
||||
private IEnumerable<string> _errors;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
if (await AuthenticationService.GetAuthenticationStatusAsync())
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
|
||||
Action<MediaPhotoResponse> backgroundSuccess = (data) =>
|
||||
{
|
||||
string imageBase64 = Convert.ToBase64String(data.Image);
|
||||
string firstColor = BitConverter.ToString(data.Background.FirstGradientColor)
|
||||
.Replace("-", string.Empty);
|
||||
string secondColor = BitConverter.ToString(data.Background.SecondGradientColor)
|
||||
.Replace("-", string.Empty);
|
||||
|
||||
_background = $"data:{data.MimeType};base64,{imageBase64}";
|
||||
_firstGradientColor = $"#{firstColor}";
|
||||
_secondGradientColor = $"#{secondColor}";
|
||||
};
|
||||
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Login()
|
||||
{
|
||||
await AccountsWebAPIService.Authenticate(_loginModel, LoginSuccess, LoginBadRequest, LoginUnauthorized);
|
||||
|
||||
async void LoginSuccess(AuthenticateResponse data)
|
||||
{
|
||||
await TokensService.SaveAuthenticationData(data);
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
|
||||
void LoginBadRequest(IDictionary<string, string[]> data)
|
||||
{
|
||||
_errors = data.SelectMany(x => x.Value).Select(x => $"• {x}");
|
||||
}
|
||||
|
||||
void LoginUnauthorized()
|
||||
{
|
||||
_errors = [ "Incorrect account data" ];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Register()
|
||||
{
|
||||
if (_registerModel.Password != _passwordConfirmation)
|
||||
{
|
||||
_errors = [ "Password fields don't match" ];
|
||||
return;
|
||||
}
|
||||
|
||||
await AccountsWebAPIService.Register(_registerModel, RegisterSuccess, RegisterBadRequest);
|
||||
|
||||
void RegisterSuccess(RegisterResponse data)
|
||||
{
|
||||
_authType = AuthType.SignIn;
|
||||
}
|
||||
|
||||
void RegisterBadRequest(IDictionary<string, string[]> data)
|
||||
{
|
||||
_errors = data.SelectMany(x => x.Value).Select(x => $"• {x}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
14
WatchIt.Website/WatchIt.Website/Pages/AuthPage.razor.css
Normal file
14
WatchIt.Website/WatchIt.Website/Pages/AuthPage.razor.css
Normal file
@@ -0,0 +1,14 @@
|
||||
/* TAGS */
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* CLASSES */
|
||||
|
||||
.logo {
|
||||
font-size: 60px;
|
||||
margin: 10px;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
@page "/"
|
||||
|
||||
<PageTitle>WatchIt</PageTitle>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
97
WatchIt.Website/WatchIt.Website/Pages/HomePage.razor
Normal file
97
WatchIt.Website/WatchIt.Website/Pages/HomePage.razor
Normal file
@@ -0,0 +1,97 @@
|
||||
@page "/"
|
||||
@using WatchIt.Common.Model.Movies
|
||||
|
||||
<PageTitle>WatchIt</PageTitle>
|
||||
|
||||
<div class="container-fluid">
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-4">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4><strong>Top 5 movies this week by popularity</strong></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
@for (int i = 0; i < 5; i++)
|
||||
{
|
||||
<div class="col">
|
||||
@if (_topMovies.Count > i)
|
||||
{
|
||||
<a class="text-reset text-decoration-none" href="/media/@_topMovies.ToArray()[i].Key.Id">
|
||||
<div class="d-flex flex-column align-items-center gap-2 h-100">
|
||||
<img class="rounded-2 shadow object-fit-cover poster-aspect-ratio" src="@(_topMovies.ToArray()[i].Value is not null ? _topMovies.ToArray()[i].Value.ToString() : "assets/poster.png")" alt="poster" width="100%"/>
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<div class="text-center border border-2 border-light rounded-circle place-circle"><strong>@(i + 1)</strong></div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="text-end ms-auto">@_topMovies.ToArray()[i].Key.Title@(_topMovies.ToArray()[i].Key.ReleaseDate.HasValue ? $" ({_topMovies.ToArray()[i].Key.ReleaseDate.Value.Year})" : string.Empty)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-4">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4><strong>Top 5 TV series this week by popularity</strong></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
@for (int i = 0; i < 5; i++)
|
||||
{
|
||||
<div class="col">
|
||||
@if (_topSeries.Count > i)
|
||||
{
|
||||
<a class="text-reset text-decoration-none" href="/media/@_topSeries.ToArray()[i].Key.Id">
|
||||
<div class="d-flex flex-column align-items-center gap-2 h-100">
|
||||
<img class="rounded-2 shadow object-fit-cover poster-aspect-ratio" src="@(_topSeries.ToArray()[i].Value is not null ? _topSeries.ToArray()[i].Value.ToString() : "assets/poster.png")" alt="poster" width="100%"/>
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<div class="text-center border border-2 border-light rounded-circle place-circle"><strong>@(i + 1)</strong></div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="text-end ms-auto">@_topSeries.ToArray()[i].Key.Title@(_topSeries.ToArray()[i].Key.ReleaseDate.HasValue ? $" ({_topSeries.ToArray()[i].Key.ReleaseDate.Value.Year})" : string.Empty)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ErrorComponent ErrorMessage="@_error"/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<LoadingComponent/>
|
||||
}
|
||||
</div>
|
||||
69
WatchIt.Website/WatchIt.Website/Pages/HomePage.razor.cs
Normal file
69
WatchIt.Website/WatchIt.Website/Pages/HomePage.razor.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Movies;
|
||||
using WatchIt.Website.Services.WebAPI.Series;
|
||||
|
||||
namespace WatchIt.Website.Pages;
|
||||
|
||||
public partial class HomePage
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
[Inject] public IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!;
|
||||
[Inject] public ISeriesWebAPIService SeriesWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded;
|
||||
private string? _error;
|
||||
|
||||
private IDictionary<MovieResponse, MediaPosterResponse?> _topMovies = new Dictionary<MovieResponse, MediaPosterResponse?>();
|
||||
private IDictionary<SeriesResponse, MediaPosterResponse?> _topSeries = new Dictionary<SeriesResponse, MediaPosterResponse?>();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
List<Task> step1Tasks = new List<Task>();
|
||||
List<Task> endTasks = new List<Task>();
|
||||
|
||||
// STEP 0
|
||||
step1Tasks.AddRange(
|
||||
[
|
||||
MoviesWebAPIService.GetMoviesViewRank(successAction: data => _topMovies = data.ToDictionary(x => x, _ => default(MediaPosterResponse?))),
|
||||
SeriesWebAPIService.GetSeriesViewRank(successAction: data => _topSeries = data.ToDictionary(x => x, _ => default(MediaPosterResponse?))),
|
||||
]);
|
||||
|
||||
// STEP 1
|
||||
await Task.WhenAll(step1Tasks);
|
||||
endTasks.AddRange(
|
||||
[
|
||||
Parallel.ForEachAsync(_topMovies, async (x, _) => await MediaWebAPIService.GetPoster(x.Key.Id, y => _topMovies[x.Key] = y)),
|
||||
Parallel.ForEachAsync(_topSeries, async (x, _) => await MediaWebAPIService.GetPoster(x.Key.Id, y => _topSeries[x.Key] = y))
|
||||
]);
|
||||
|
||||
// END
|
||||
await Task.WhenAll(endTasks);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
16
WatchIt.Website/WatchIt.Website/Pages/HomePage.razor.css
Normal file
16
WatchIt.Website/WatchIt.Website/Pages/HomePage.razor.css
Normal file
@@ -0,0 +1,16 @@
|
||||
/* CLASSES */
|
||||
|
||||
.poster-aspect-ratio {
|
||||
aspect-ratio: 3/5;
|
||||
}
|
||||
|
||||
.border-2 {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.place-circle {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
vertical-align: middle;
|
||||
line-height: 25px;
|
||||
}
|
||||
230
WatchIt.Website/WatchIt.Website/Pages/MediaEditPage.razor
Normal file
230
WatchIt.Website/WatchIt.Website/Pages/MediaEditPage.razor
Normal file
@@ -0,0 +1,230 @@
|
||||
@using WatchIt.Common.Model.Movies
|
||||
@using WatchIt.Common.Model.Series
|
||||
|
||||
@page "/media/{id:long}/edit"
|
||||
@page "/media/new/{type}"
|
||||
|
||||
|
||||
<PageTitle>
|
||||
WatchIt -
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error) && _user?.IsAdmin == true)
|
||||
{
|
||||
if (_media is not null)
|
||||
{
|
||||
@($"Edit \"")@(_media.Title)@("\"")
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_movieRequest is null)
|
||||
{
|
||||
@("New TV series")
|
||||
}
|
||||
else
|
||||
{
|
||||
@("New movie")
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@("Error")
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@("Loading")
|
||||
}
|
||||
</PageTitle>
|
||||
|
||||
<div class="container-fluid">
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error))
|
||||
{
|
||||
if (_user?.IsAdmin == true)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-2">
|
||||
<h2 class="m-0 mx-2 mb-1 p-0">@(_media is not null ? "Edit" : "Create new") @(_movieRequest is not null ? "movie" : "series")@(_media is not null ? $" \"{_media.Title}\"" : string.Empty)</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3 gx-3">
|
||||
<div class="col-auto">
|
||||
<div class="rounded-3 panel panel-regular p-4 h-100">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img class="rounded-2 shadow object-fit-cover" src="@(_mediaPosterRequest is not null ? _mediaPosterRequest.ToString() : "assets/poster.png")" alt="poster" width="300" height="500"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<InputFile class="form-control" OnChange="LoadPoster" disabled="@(_media is null)" autocomplete="off" style="width: 300px;"/>
|
||||
</div>
|
||||
</div>
|
||||
@if (_mediaPosterChanged || _mediaPosterSaved is not null)
|
||||
{
|
||||
<div class="row mt-4 gx-1" style="width: 300px;">
|
||||
@if (_mediaPosterChanged)
|
||||
{
|
||||
<div class="col">
|
||||
<button type="button" class="btn btn-secondary btn-block btn-stretch-x" @onclick="SavePoster" disabled=@(!Id.HasValue || _mediaPosterSaving || _mediaPosterDeleting) autocomplete="off">
|
||||
@if (!_mediaPosterSaving)
|
||||
{
|
||||
<span class="sr-only">Save poster</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Saving...</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button type="button" class="btn btn-danger btn-block btn-stretch-x" @onclick="CancelPoster" disabled=@(!Id.HasValue || _mediaPosterSaving || _mediaPosterDeleting) autocomplete="off">Drop changes</button>
|
||||
</div>
|
||||
}
|
||||
else if (_mediaPosterSaved is not null)
|
||||
{
|
||||
<div class="col">
|
||||
<button type="button" class="btn btn-danger btn-block btn-stretch-x" @onclick="DeletePoster" disabled=@(!Id.HasValue || _mediaPosterSaving || _mediaPosterDeleting) autocomplete="off">
|
||||
@if (!_mediaPosterSaving)
|
||||
{
|
||||
<span class="sr-only">Delete poster</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Deleting...</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-4 h-100">
|
||||
<EditForm Model="_mediaRequest">
|
||||
<AntiforgeryToken/>
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row form-group mb-1">
|
||||
<label for="title" class="col-2 col-form-label">Title*</label>
|
||||
<div class="col-10">
|
||||
<InputText id="title" class="form-control" @bind-Value="_mediaRequest!.Title"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group my-1">
|
||||
<label for="og-title" class="col-2 col-form-label">Original title</label>
|
||||
<div class="col-10">
|
||||
<InputText id="og-title" class="form-control" @bind-Value="_mediaRequest!.OriginalTitle"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group my-1">
|
||||
<label for="desc" class="col-2 col-form-label">Description</label>
|
||||
<div class="col-10">
|
||||
<InputTextArea id="desc" class="form-control" @bind-Value="_mediaRequest!.Description"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group my-1">
|
||||
<label for="rel-date" class="col-2 col-form-label">Release date</label>
|
||||
<div class="col-4">
|
||||
<InputDate TValue="DateOnly?" id="rel-date" class="form-control" @bind-Value="_mediaRequest!.ReleaseDate"/>
|
||||
</div>
|
||||
<label for="length" class="col-2 col-form-label">Length</label>
|
||||
<div class="col-4">
|
||||
<InputNumber TValue="short?" id="length" class="form-control" @bind-Value="_mediaRequest!.Length"/>
|
||||
</div>
|
||||
</div>
|
||||
@if (_mediaRequest is MovieRequest)
|
||||
{
|
||||
<div class="row form-group mt-1">
|
||||
<label for="budget" class="col-2 col-form-label">Budget</label>
|
||||
<div class="col-10">
|
||||
<InputNumber TValue="decimal?" id="budget" class="form-control" @bind-Value="((MovieRequest)_mediaRequest)!.Budget"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row form-group mt-1">
|
||||
<label class="col-2 col-form-label">Has ended</label>
|
||||
<div class="col-10 col-form-label">
|
||||
<div class="d-flex gap-3">
|
||||
<InputRadioGroup TValue="bool" @bind-Value="((SeriesRequest)_mediaRequest)!.HasEnded">
|
||||
<div class="d-flex gap-2">
|
||||
<InputRadio TValue="bool" Value="true"/>
|
||||
Yes
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<InputRadio TValue="bool" Value="false"/>
|
||||
No
|
||||
</div>
|
||||
</InputRadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-end align-items-center gap-3">
|
||||
@if (!string.IsNullOrWhiteSpace(_basicDataError))
|
||||
{
|
||||
<div class="text-danger">@_basicDataError</div>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" @onclick="SaveBasicData">
|
||||
@if (!_basicDataSaving)
|
||||
{
|
||||
<span class="sr-only">Save</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>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ErrorComponent ErrorMessage="You do not have permission to view this site"/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<ErrorComponent ErrorMessage="@_error"/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<LoadingComponent/>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@if (_background is not null)
|
||||
{
|
||||
<style>
|
||||
body {
|
||||
background-image: url('@($"data:{_background.MimeType};base64,{Convert.ToBase64String(_background.Image)}")') !important;
|
||||
}
|
||||
|
||||
.logo, .main-button {
|
||||
background-image: linear-gradient(45deg, @($"#{BitConverter.ToString(_background.Background.FirstGradientColor).Replace("-", string.Empty)}"), @($"#{BitConverter.ToString(_background.Background.SecondGradientColor).Replace("-", string.Empty)}")) !important;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
254
WatchIt.Website/WatchIt.Website/Pages/MediaEditPage.razor.cs
Normal file
254
WatchIt.Website/WatchIt.Website/Pages/MediaEditPage.razor.cs
Normal file
@@ -0,0 +1,254 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Movies;
|
||||
using WatchIt.Website.Services.WebAPI.Series;
|
||||
|
||||
namespace WatchIt.Website.Pages;
|
||||
|
||||
public partial class MediaEditPage : ComponentBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
[Inject] public IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!;
|
||||
[Inject] public ISeriesWebAPIService SeriesWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter] public long? Id { get; set; }
|
||||
[Parameter] public string? Type { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded = false;
|
||||
private string? _error;
|
||||
|
||||
private User? _user;
|
||||
|
||||
private MediaPhotoResponse? _background;
|
||||
|
||||
private MediaResponse? _media;
|
||||
private MovieRequest? _movieRequest;
|
||||
private SeriesRequest? _seriesRequest;
|
||||
private Media? _mediaRequest => _movieRequest is not null ? _movieRequest : _seriesRequest;
|
||||
private bool _basicDataSaving;
|
||||
private string? _basicDataError;
|
||||
|
||||
private MediaPosterResponse? _mediaPosterSaved;
|
||||
private MediaPosterRequest? _mediaPosterRequest;
|
||||
private bool _mediaPosterChanged;
|
||||
private bool _mediaPosterSaving;
|
||||
private bool _mediaPosterDeleting;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
List<Task> step1Tasks = new List<Task>();
|
||||
List<Task> step2Tasks = new List<Task>();
|
||||
List<Task> endTasks = new List<Task>();
|
||||
|
||||
// STEP 0
|
||||
step1Tasks.AddRange(
|
||||
[
|
||||
Task.Run(async () => _user = await AuthenticationService.GetUserAsync())
|
||||
]);
|
||||
|
||||
// STEP 1
|
||||
await Task.WhenAll(step1Tasks);
|
||||
if (_user is not null && _user.IsAdmin)
|
||||
{
|
||||
step2Tasks.AddRange(
|
||||
[
|
||||
InitializeMedia()
|
||||
]);
|
||||
}
|
||||
|
||||
// STEP 2
|
||||
await Task.WhenAll(step2Tasks);
|
||||
if (_user is not null && _user.IsAdmin && _media is not null)
|
||||
{
|
||||
endTasks.AddRange(
|
||||
[
|
||||
MediaWebAPIService.GetPhotoMediaRandomBackground(Id.Value, data => _background = data),
|
||||
MediaWebAPIService.GetPoster(Id.Value, data =>
|
||||
{
|
||||
_mediaPosterSaved = data;
|
||||
_mediaPosterRequest = new MediaPosterRequest(data);
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
await Task.WhenAll(endTasks);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeMedia()
|
||||
{
|
||||
if (Id.HasValue)
|
||||
{
|
||||
await MediaWebAPIService.GetMedia(Id.Value, data => _media = data, () => NavigationManager.NavigateTo("/media/new/movie"));
|
||||
if (_media.Type == MediaType.Movie)
|
||||
{
|
||||
await MoviesWebAPIService.GetMovie(Id.Value, data => _movieRequest = new MovieRequest(data));
|
||||
}
|
||||
else
|
||||
{
|
||||
await SeriesWebAPIService.GetSeries(Id.Value, data => _seriesRequest = new SeriesRequest(data));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(Type) && Type == "series")
|
||||
{
|
||||
_seriesRequest = new SeriesRequest
|
||||
{
|
||||
Title = string.Empty
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
_movieRequest = new MovieRequest
|
||||
{
|
||||
Title = string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Poster
|
||||
|
||||
private async Task LoadPoster(InputFileChangeEventArgs args)
|
||||
{
|
||||
if (args.File.ContentType.StartsWith("image"))
|
||||
{
|
||||
Stream stream = args.File.OpenReadStream(5242880);
|
||||
byte[] array;
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
await stream.CopyToAsync(ms);
|
||||
array = ms.ToArray();
|
||||
}
|
||||
|
||||
_mediaPosterRequest = new MediaPosterRequest()
|
||||
{
|
||||
Image = array,
|
||||
MimeType = args.File.ContentType
|
||||
};
|
||||
_mediaPosterChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SavePoster()
|
||||
{
|
||||
void Success(MediaPosterResponse data)
|
||||
{
|
||||
_mediaPosterSaved = data;
|
||||
_mediaPosterRequest = new MediaPosterRequest(data);
|
||||
_mediaPosterChanged = false;
|
||||
_mediaPosterSaving = false;
|
||||
}
|
||||
|
||||
_mediaPosterSaving = true;
|
||||
await MediaWebAPIService.PutPoster(Id.Value, _mediaPosterRequest, Success);
|
||||
}
|
||||
|
||||
private void CancelPoster()
|
||||
{
|
||||
_mediaPosterRequest = _mediaPosterSaved is not null ? new MediaPosterRequest(_mediaPosterSaved) : null;
|
||||
_mediaPosterChanged = false;
|
||||
}
|
||||
|
||||
private async Task DeletePoster()
|
||||
{
|
||||
void Success()
|
||||
{
|
||||
_mediaPosterSaved = null;
|
||||
_mediaPosterRequest = null;
|
||||
_mediaPosterChanged = false;
|
||||
_mediaPosterDeleting = false;
|
||||
}
|
||||
|
||||
_mediaPosterDeleting = true;
|
||||
await MediaWebAPIService.DeletePoster(Id.Value, Success);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Basic data
|
||||
|
||||
private async Task SaveBasicData()
|
||||
{
|
||||
void SuccessPost(long id)
|
||||
{
|
||||
_basicDataSaving = false;
|
||||
NavigationManager.NavigateTo($"/media/{id}/edit", true);
|
||||
}
|
||||
|
||||
void BadRequest(IDictionary<string, string[]> errors)
|
||||
{
|
||||
_basicDataError = errors.SelectMany(x => x.Value).FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(_basicDataError))
|
||||
{
|
||||
_basicDataSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
_basicDataSaving = true;
|
||||
if (_media is null)
|
||||
{
|
||||
if (_movieRequest is not null)
|
||||
{
|
||||
await MoviesWebAPIService.PostMovie(_movieRequest, data => SuccessPost(data.Id), BadRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SeriesWebAPIService.PostSeries(_seriesRequest, data => SuccessPost(data.Id), BadRequest);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_movieRequest is not null)
|
||||
{
|
||||
await MoviesWebAPIService.PutMovie(Id.Value, _movieRequest, () => _basicDataSaving = false, BadRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SeriesWebAPIService.PutSeries(Id.Value, _seriesRequest, () => _basicDataSaving = false, BadRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
218
WatchIt.Website/WatchIt.Website/Pages/MediaPage.razor
Normal file
218
WatchIt.Website/WatchIt.Website/Pages/MediaPage.razor
Normal file
@@ -0,0 +1,218 @@
|
||||
@using System.Text
|
||||
@using Microsoft.IdentityModel.Tokens
|
||||
@using WatchIt.Common.Model.Genres
|
||||
|
||||
@page "/media/{id:long}"
|
||||
|
||||
@layout MainLayout
|
||||
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error))
|
||||
{
|
||||
<PageTitle>@_media.Title@(_media.ReleaseDate is not null ? $" ({_media.ReleaseDate.Value.Year})" : string.Empty) - WatchIt</PageTitle>
|
||||
}
|
||||
else
|
||||
{
|
||||
<PageTitle>Error - WatchIt</PageTitle>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<PageTitle>Loading... - WatchIt</PageTitle>
|
||||
}
|
||||
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error))
|
||||
{
|
||||
<div class="row mt-9">
|
||||
<div class="col-auto">
|
||||
<img class="rounded-2 shadow object-fit-cover" src="@(_poster is not null ? _poster.ToString() : "assets/poster.png")" alt="poster" width="200" height="333"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="d-flex h-100">
|
||||
<div class="container-fluid px-0 align-self-end">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="align-self-end title-shadow">
|
||||
<strong>@_media.Title</strong>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(_media.Description))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="description-shadow">
|
||||
@_media.Description
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3 gx-3">
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-4 h-100">
|
||||
<div class="container-fluid px-0">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<div class="metadata-pill">
|
||||
<strong>@(_media.Type == MediaType.Movie ? "Movie" : "TV Series")</strong>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(_media.OriginalTitle))
|
||||
{
|
||||
<div class="metadata-pill">
|
||||
<strong>Original title:</strong> @_media.OriginalTitle
|
||||
</div>
|
||||
}
|
||||
@if (_media.ReleaseDate is not null)
|
||||
{
|
||||
<div class="metadata-pill">
|
||||
<strong>Release date:</strong> @_media.ReleaseDate.ToString()
|
||||
</div>
|
||||
}
|
||||
@if (_media.Length is not null)
|
||||
{
|
||||
<div class="metadata-pill">
|
||||
<strong>Length:</strong> @TimeSpan.FromMinutes(_media.Length.Value).ToString(@"hh\:mm")
|
||||
</div>
|
||||
}
|
||||
@if (_movie?.Budget is not null)
|
||||
{
|
||||
<div class="metadata-pill">
|
||||
<strong>Budget:</strong> @(Math.Round(_movie.Budget.Value))$
|
||||
</div>
|
||||
}
|
||||
@if (_series?.HasEnded == true)
|
||||
{
|
||||
<div class="metadata-pill">
|
||||
Ended
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<h4><strong>Genres:</strong></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
@if (_genres.IsNullOrEmpty())
|
||||
{
|
||||
<div>
|
||||
No genres assigned.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (GenreResponse genre in _genres)
|
||||
{
|
||||
<a class="text-decoration-none text-light" href="/genre/@genre.Id">
|
||||
<div class="metadata-pill">
|
||||
@genre.Name
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="rounded-3 panel panel-yellow p-4 h-100">
|
||||
<div class="container-fluid px-0">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4 class="text-dark">
|
||||
<strong>Global rating:</strong> @(_globalRating.RatingCount == 0 ? "no ratings" : $"{Math.Round(_globalRating.RatingAverage, 1)}/10")
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<hr class="rating-separator"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4 class="text-dark">
|
||||
<strong>Your rating:</strong>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@if (_user is not null)
|
||||
{
|
||||
<div class="d-flex rating">
|
||||
<input id="r1" type="radio" name="rate" checked="@(_userRating == 1 )" onclick="@(async () => await AddRating(1 ))"/>
|
||||
<label class="text-dark" for="r1">@(_userRating >= 1 ? "\u2605" : "\u2606")</label>
|
||||
<input id="r2" type="radio" name="rate" checked="@(_userRating == 2 )" onclick="@(async () => await AddRating(2 ))"/>
|
||||
<label class="text-dark" for="r2">@(_userRating >= 2 ? "\u2605" : "\u2606")</label>
|
||||
<input id="r3" type="radio" name="rate" checked="@(_userRating == 3 )" onclick="@(async () => await AddRating(3 ))"/>
|
||||
<label class="text-dark" for="r3">@(_userRating >= 3 ? "\u2605" : "\u2606")</label>
|
||||
<input id="r4" type="radio" name="rate" checked="@(_userRating == 4 )" onclick="@(async () => await AddRating(4 ))"/>
|
||||
<label class="text-dark" for="r4">@(_userRating >= 4 ? "\u2605" : "\u2606")</label>
|
||||
<input id="r5" type="radio" name="rate" checked="@(_userRating == 5 )" onclick="@(async () => await AddRating(5 ))"/>
|
||||
<label class="text-dark" for="r5">@(_userRating >= 5 ? "\u2605" : "\u2606")</label>
|
||||
<input id="r6" type="radio" name="rate" checked="@(_userRating == 6 )" onclick="@(async () => await AddRating(6 ))"/>
|
||||
<label class="text-dark" for="r6">@(_userRating >= 6 ? "\u2605" : "\u2606")</label>
|
||||
<input id="r7" type="radio" name="rate" checked="@(_userRating == 7 )" onclick="@(async () => await AddRating(7 ))"/>
|
||||
<label class="text-dark" for="r7">@(_userRating >= 7 ? "\u2605" : "\u2606")</label>
|
||||
<input id="r8" type="radio" name="rate" checked="@(_userRating == 8 )" onclick="@(async () => await AddRating(8 ))"/>
|
||||
<label class="text-dark" for="r8">@(_userRating >= 8 ? "\u2605" : "\u2606")</label>
|
||||
<input id="r9" type="radio" name="rate" checked="@(_userRating == 9 )" onclick="@(async () => await AddRating(9 ))"/>
|
||||
<label class="text-dark" for="r9">@(_userRating >= 9 ? "\u2605" : "\u2606")</label>
|
||||
<input id="r10" type="radio" name="rate" checked="@(_userRating == 10)" onclick="@(async () => await AddRating(10))"/>
|
||||
<label class="text-dark" for="r10">@(_userRating == 10 ? "\u2605" : "\u2606")</label>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-dark">You must be logged in to add a rating</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ErrorComponent ErrorMessage="@_error"/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<LoadingComponent/>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@if (_background is not null)
|
||||
{
|
||||
<style>
|
||||
body {
|
||||
background-image: url('@($"data:{_background.MimeType};base64,{Convert.ToBase64String(_background.Image)}")') !important;
|
||||
}
|
||||
|
||||
.logo, .main-button {
|
||||
background-image: linear-gradient(45deg, @($"#{BitConverter.ToString(_background.Background.FirstGradientColor).Replace("-", string.Empty)}"), @($"#{BitConverter.ToString(_background.Background.SecondGradientColor).Replace("-", string.Empty)}")) !important;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
128
WatchIt.Website/WatchIt.Website/Pages/MediaPage.razor.cs
Normal file
128
WatchIt.Website/WatchIt.Website/Pages/MediaPage.razor.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model.Genres;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Movies;
|
||||
using WatchIt.Website.Services.WebAPI.Series;
|
||||
|
||||
namespace WatchIt.Website.Pages;
|
||||
|
||||
public partial class MediaPage : ComponentBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
[Inject] public IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!;
|
||||
[Inject] public ISeriesWebAPIService SeriesWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter] public long Id { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded;
|
||||
private string? _error;
|
||||
|
||||
private MediaResponse? _media;
|
||||
|
||||
private User? _user;
|
||||
|
||||
private MediaPhotoResponse? _background;
|
||||
private MediaPosterResponse? _poster;
|
||||
private IEnumerable<GenreResponse> _genres;
|
||||
private MediaRatingResponse _globalRating;
|
||||
private MovieResponse? _movie;
|
||||
private SeriesResponse? _series;
|
||||
|
||||
private short? _userRating;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
List<Task> step1Tasks = new List<Task>();
|
||||
List<Task> step2Tasks = new List<Task>();
|
||||
List<Task> endTasks = new List<Task>();
|
||||
|
||||
// STEP 0
|
||||
step1Tasks.AddRange(
|
||||
[
|
||||
MediaWebAPIService.GetMedia(Id, data => _media = data, () => _error = $"Media with id {Id} was not found")
|
||||
]);
|
||||
|
||||
// STEP 1
|
||||
await Task.WhenAll(step1Tasks);
|
||||
if (_error is null)
|
||||
{
|
||||
step2Tasks.AddRange(
|
||||
[
|
||||
Task.Run(async () => _user = await AuthenticationService.GetUserAsync())
|
||||
]);
|
||||
|
||||
endTasks.AddRange(
|
||||
[
|
||||
MediaWebAPIService.PostMediaView(Id),
|
||||
MediaWebAPIService.GetPhotoMediaRandomBackground(Id, data => _background = data),
|
||||
MediaWebAPIService.GetPoster(Id, data => _poster = data),
|
||||
MediaWebAPIService.GetMediaGenres(Id, data => _genres = data),
|
||||
MediaWebAPIService.GetMediaRating(Id, data => _globalRating = data),
|
||||
_media.Type == MediaType.Movie ? MoviesWebAPIService.GetMovie(Id, data => _movie = data) : SeriesWebAPIService.GetSeries(Id, data => _series = data),
|
||||
]);
|
||||
}
|
||||
|
||||
// STEP 2
|
||||
await Task.WhenAll(step2Tasks);
|
||||
if (_error is null && _user is not null)
|
||||
{
|
||||
endTasks.AddRange(
|
||||
[
|
||||
MediaWebAPIService.GetMediaRatingByUser(Id, _user.Id, data => _userRating = data)
|
||||
]);
|
||||
}
|
||||
|
||||
// END
|
||||
await Task.WhenAll(endTasks);
|
||||
|
||||
_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
|
||||
}
|
||||
38
WatchIt.Website/WatchIt.Website/Pages/MediaPage.razor.css
Normal file
38
WatchIt.Website/WatchIt.Website/Pages/MediaPage.razor.css
Normal file
@@ -0,0 +1,38 @@
|
||||
/* CLASSES */
|
||||
|
||||
.metadata-pill {
|
||||
border-color: gray;
|
||||
border-width: 2px;
|
||||
border-radius: 30px;
|
||||
border-style: solid;
|
||||
|
||||
padding: 2px 10px;
|
||||
}
|
||||
|
||||
.title-shadow {
|
||||
text-shadow: 2px 2px 2px #000;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Movies;
|
||||
using WatchIt.Website.Services.WebAPI.Series;
|
||||
|
||||
namespace WatchIt.Website;
|
||||
|
||||
@@ -13,6 +20,7 @@ public static class Program
|
||||
{
|
||||
WebApplication app = WebApplication.CreateBuilder(args)
|
||||
.SetupServices()
|
||||
.SetupAuthentication()
|
||||
.SetupApplication()
|
||||
.Build();
|
||||
|
||||
@@ -40,18 +48,30 @@ public static class Program
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
|
||||
private static WebApplicationBuilder SetupServices(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddSingleton<HttpClient>();
|
||||
|
||||
// Utility
|
||||
builder.Services.AddSingleton<IHttpClientService, HttpClientService>();
|
||||
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
|
||||
builder.Services.AddScoped<ITokensService, TokensService>();
|
||||
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
|
||||
|
||||
// WebAPI
|
||||
builder.Services.AddSingleton<IAccountsWebAPIService, AccountsWebAPIService>();
|
||||
builder.Services.AddScoped<IAccountsWebAPIService, AccountsWebAPIService>();
|
||||
builder.Services.AddSingleton<IMediaWebAPIService, MediaWebAPIService>();
|
||||
builder.Services.AddSingleton<IMoviesWebAPIService, MoviesWebAPIService>();
|
||||
builder.Services.AddSingleton<ISeriesWebAPIService, SeriesWebAPIService>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static WebApplicationBuilder SetupAuthentication(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddAuthorizationCore();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider, JWTAuthenticationStateProvider>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,14 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" />
|
||||
<ProjectReference Include="..\..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Tokens\WatchIt.WebAPI.Services.Utility.Tokens.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Authentication\WatchIt.Website.Services.Utility.Authentication.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Tokens\WatchIt.Website.Services.Utility.Tokens.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Accounts\WatchIt.Website.Services.WebAPI.Accounts.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Media\WatchIt.Website.Services.WebAPI.Media.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Movies\WatchIt.Website.Services.WebAPI.Movies.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Series\WatchIt.Website.Services.WebAPI.Series.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -27,16 +32,12 @@
|
||||
<_ContentIncludedByDefault Remove="Components\Pages\Error.razor" />
|
||||
<_ContentIncludedByDefault Remove="Components\Pages\Home.razor" />
|
||||
<_ContentIncludedByDefault Remove="Components\Pages\Weather.razor" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css.map" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Layout\MainLayout.razor" />
|
||||
<AdditionalFiles Include="Pages\Home.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Components\" />
|
||||
<Folder Include="wwwroot\assets\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -7,4 +7,11 @@
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using WatchIt.Website
|
||||
@using WatchIt.Website.Layout
|
||||
@using WatchIt.Website.Layout
|
||||
@using WatchIt.Website.Components
|
||||
@using WatchIt.Common.Model.Accounts
|
||||
@using WatchIt.Common.Model.Media
|
||||
@using WatchIt.Website.Services.Utility.Tokens
|
||||
@using WatchIt.Website.Services.Utility.Authentication
|
||||
@using WatchIt.Website.Services.WebAPI.Accounts
|
||||
@using WatchIt.Website.Services.WebAPI.Media
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user