2
.github/config/gitversion.yml
vendored
2
.github/config/gitversion.yml
vendored
@@ -1,4 +1,4 @@
|
||||
next-version: 0.1.0
|
||||
next-version: 0.2.0
|
||||
assembly-versioning-scheme: MajorMinorPatch
|
||||
assembly-file-versioning-scheme: MajorMinorPatch
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
name: "[Pull request][dev] Build and test"
|
||||
name: "Pull request to 'dev' branch"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "dev" ]
|
||||
branches:
|
||||
- "dev/**"
|
||||
paths:
|
||||
- 'WatchIt**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
name: Dotnet solution build test and unit tests execution
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
26
.github/workflows/dev_push.yml
vendored
Normal file
26
.github/workflows/dev_push.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: "Push to 'dev' branch"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "dev/**"
|
||||
paths:
|
||||
- 'WatchIt**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Dotnet solution build test and unit tests execution
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
45
.github/workflows/master_pr.yml
vendored
Normal file
45
.github/workflows/master_pr.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: "Pull request to 'master' branch"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
|
||||
jobs:
|
||||
build-dotnet:
|
||||
name: Dotnet solution build test and unit tests execution
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build solution
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
build-docker:
|
||||
name: Docker image build test
|
||||
needs: build-dotnet
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
app:
|
||||
- WatchIt.WebAPI
|
||||
- WatchIt.Website
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Convert name to lowercase
|
||||
id: name
|
||||
uses: ASzc/change-string-case-action@v1
|
||||
with:
|
||||
string: ${{ matrix.app }}
|
||||
- name: Build image
|
||||
run: docker build ${{ github.workspace }} -t ghcr.io/${{github.actor}}/${{ steps.name.outputs.lowercase }}:build-test -f ${{ github.workspace }}/${{ matrix.app }}/${{ matrix.app }}/Dockerfile
|
||||
17
.github/workflows/master_push.yml
vendored
17
.github/workflows/master_push.yml
vendored
@@ -6,26 +6,9 @@ on:
|
||||
- "master"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
strategy:
|
||||
matrix:
|
||||
app:
|
||||
|
||||
@@ -2,20 +2,8 @@
|
||||
|
||||
namespace WatchIt.Common.Model.Accounts;
|
||||
|
||||
public abstract class AccountProfilePicture
|
||||
public abstract class AccountProfilePicture : Picture
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("image")]
|
||||
public required byte[] Image { get; set; }
|
||||
|
||||
[JsonPropertyName("mime_type")]
|
||||
public required string MimeType { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
|
||||
@@ -21,9 +21,9 @@ public class GenreQueryParameters : QueryParameters<GenreResponse>
|
||||
|
||||
public override bool IsMeetingConditions(GenreResponse item) =>
|
||||
(
|
||||
TestString(item.Name, Name)
|
||||
TestStringWithRegex(item.Name, Name)
|
||||
&&
|
||||
TestString(item.Description, Description)
|
||||
TestStringWithRegex(item.Description, Description)
|
||||
);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using WatchIt.Common.Query;
|
||||
|
||||
namespace WatchIt.Common.Model.Genres;
|
||||
|
||||
public class GenreResponse : Genre
|
||||
public class GenreResponse : Genre, IQueryOrderable<GenreResponse>
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonIgnore]
|
||||
public static IDictionary<string, Func<GenreResponse, IComparable>> OrderableProperties { get; } = new Dictionary<string, Func<GenreResponse, IComparable>>
|
||||
{
|
||||
{ "id", x => x.Id },
|
||||
{ "name", x => x.Name },
|
||||
{ "description", x => x.Description }
|
||||
};
|
||||
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public class MediaPhoto
|
||||
{
|
||||
[JsonPropertyName("media_id")]
|
||||
public required long MediaId { get; set; }
|
||||
|
||||
[JsonPropertyName("image")]
|
||||
public required byte[] Image { get; set; }
|
||||
|
||||
[JsonPropertyName("mime_type")]
|
||||
public required string MimeType { get; set; }
|
||||
|
||||
[JsonPropertyName("background")]
|
||||
public MediaPhotoBackground? Background { get; set; }
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WatchIt.Common.Query;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public class MediaPhotoQueryParameters : QueryParameters<MediaPhotoResponse>
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[FromQuery(Name = "mime_type")]
|
||||
public string? MimeType { get; set; }
|
||||
|
||||
[FromQuery(Name = "is_background")]
|
||||
public bool? IsBackground { get; set; }
|
||||
|
||||
[FromQuery(Name = "is_universal_background")]
|
||||
public bool? IsUniversalBackground { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public override bool IsMeetingConditions(MediaPhotoResponse item) =>
|
||||
(
|
||||
TestString(item.MimeType, MimeType)
|
||||
&&
|
||||
TestBoolean(item.Background is not null, IsBackground)
|
||||
&&
|
||||
TestBoolean(item.Background!.IsUniversalBackground, IsUniversalBackground)
|
||||
);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,12 +1,31 @@
|
||||
using WatchIt.Database.Model.Media;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public class MediaPhotoRequest : MediaPhoto
|
||||
public class MediaPhotoRequest : Photo
|
||||
{
|
||||
public MediaPhotoImage CreateMediaPhotoImage() => new MediaPhotoImage
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MediaPhotoRequest() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public MediaPhotoRequest(PhotoResponse response)
|
||||
{
|
||||
MediaId = MediaId,
|
||||
Image = response.Image;
|
||||
MimeType = response.MimeType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public MediaPhotoImage CreateMediaPhotoImage(long mediaId) => new MediaPhotoImage
|
||||
{
|
||||
MediaId = mediaId,
|
||||
Image = Image,
|
||||
MimeType = MimeType
|
||||
};
|
||||
@@ -19,21 +38,5 @@ public class MediaPhotoRequest : MediaPhoto
|
||||
SecondGradientColor = Background.SecondGradientColor
|
||||
};
|
||||
|
||||
public void UpdateMediaPhotoImage(MediaPhotoImage item)
|
||||
{
|
||||
item.MediaId = MediaId;
|
||||
item.Image = Image;
|
||||
item.MimeType = MimeType;
|
||||
item.UploadDate = DateTime.Now;
|
||||
}
|
||||
|
||||
public void UpdateMediaPhotoImageBackground(MediaPhotoImageBackground item)
|
||||
{
|
||||
if (Background is not null)
|
||||
{
|
||||
item.IsUniversalBackground = Background.IsUniversalBackground;
|
||||
item.FirstGradientColor = Background.FirstGradientColor;
|
||||
item.SecondGradientColor = Background.SecondGradientColor;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -2,23 +2,6 @@
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public abstract class MediaPoster
|
||||
public abstract class MediaPoster : Picture
|
||||
{
|
||||
#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,84 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WatchIt.Common.Query;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public class MediaQueryParameters : QueryParameters<MediaResponse>
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[FromQuery(Name = "type")]
|
||||
public MediaType? Type { get; set; }
|
||||
|
||||
[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 = "rating_average")]
|
||||
public decimal? RatingAverage { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_average_from")]
|
||||
public decimal? RatingAverageFrom { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_average_to")]
|
||||
public decimal? RatingAverageTo { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_count")]
|
||||
public long? RatingCount { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_count_from")]
|
||||
public long? RatingCountFrom { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_count_to")]
|
||||
public long? RatingCountTo { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public override bool IsMeetingConditions(MediaResponse item) =>
|
||||
(
|
||||
Test(item.Type, Type)
|
||||
&&
|
||||
TestStringWithRegex(item.Title, Title)
|
||||
&&
|
||||
TestStringWithRegex(item.OriginalTitle, OriginalTitle)
|
||||
&&
|
||||
TestStringWithRegex(item.Description, Description)
|
||||
&&
|
||||
TestComparable(item.ReleaseDate, ReleaseDate, ReleaseDateFrom, ReleaseDateTo)
|
||||
&&
|
||||
TestComparable(item.Length, Length, LengthFrom, LengthTo)
|
||||
&&
|
||||
TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo)
|
||||
&&
|
||||
TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo)
|
||||
);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,18 +1,37 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.Common.Query;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
|
||||
public class MediaResponse : Media
|
||||
public class MediaResponse : Media, IQueryOrderable<MediaResponse>
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonIgnore]
|
||||
public static IDictionary<string, Func<MediaResponse, IComparable>> OrderableProperties { get; } = new Dictionary<string, Func<MediaResponse, IComparable>>
|
||||
{
|
||||
{ "id", x => x.Id },
|
||||
{ "title", x => x.Title },
|
||||
{ "original_title", x => x.OriginalTitle },
|
||||
{ "description", x => x.Description },
|
||||
{ "release_date", x => x.ReleaseDate },
|
||||
{ "length", x => x.Length },
|
||||
{ "rating.average", x => x.Rating.Average },
|
||||
{ "rating.count", x => x.Rating.Count }
|
||||
};
|
||||
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public MediaType Type { get; set; }
|
||||
|
||||
[JsonPropertyName("rating")]
|
||||
public RatingResponse Rating { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -32,6 +51,7 @@ public class MediaResponse : Media
|
||||
ReleaseDate = media.ReleaseDate;
|
||||
Length = media.Length;
|
||||
Type = mediaType;
|
||||
Rating = new RatingResponse(media.RatingMedia);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -43,6 +43,24 @@ public class MovieQueryParameters : QueryParameters<MovieResponse>
|
||||
[FromQuery(Name = "budget_to")]
|
||||
public decimal? BudgetTo { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_average")]
|
||||
public decimal? RatingAverage { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_average_from")]
|
||||
public decimal? RatingAverageFrom { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_average_to")]
|
||||
public decimal? RatingAverageTo { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_count")]
|
||||
public long? RatingCount { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_count_from")]
|
||||
public long? RatingCountFrom { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_count_to")]
|
||||
public long? RatingCountTo { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -51,17 +69,21 @@ public class MovieQueryParameters : QueryParameters<MovieResponse>
|
||||
|
||||
public override bool IsMeetingConditions(MovieResponse item) =>
|
||||
(
|
||||
TestString(item.Title, Title)
|
||||
TestStringWithRegex(item.Title, Title)
|
||||
&&
|
||||
TestString(item.OriginalTitle, OriginalTitle)
|
||||
TestStringWithRegex(item.OriginalTitle, OriginalTitle)
|
||||
&&
|
||||
TestString(item.Description, Description)
|
||||
TestStringWithRegex(item.Description, Description)
|
||||
&&
|
||||
TestComparable(item.ReleaseDate, ReleaseDate, ReleaseDateFrom, ReleaseDateTo)
|
||||
&&
|
||||
TestComparable(item.Length, Length, LengthFrom, LengthTo)
|
||||
&&
|
||||
TestComparable(item.Budget, Budget, BudgetFrom, BudgetTo)
|
||||
&&
|
||||
TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo)
|
||||
&&
|
||||
TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo)
|
||||
);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.Common.Query;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Movies;
|
||||
|
||||
public class MovieResponse : Movie
|
||||
public class MovieResponse : Movie, IQueryOrderable<MovieResponse>
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonIgnore]
|
||||
public static IDictionary<string, Func<MovieResponse, IComparable>> OrderableProperties { get; } = new Dictionary<string, Func<MovieResponse, IComparable>>
|
||||
{
|
||||
{ "id", x => x.Id },
|
||||
{ "title", x => x.Title },
|
||||
{ "original_title", x => x.OriginalTitle },
|
||||
{ "description", x => x.Description },
|
||||
{ "release_date", x => x.ReleaseDate },
|
||||
{ "length", x => x.Length },
|
||||
{ "budget", x => x.Budget },
|
||||
{ "rating.average", x => x.Rating.Average },
|
||||
{ "rating.count", x => x.Rating.Count }
|
||||
};
|
||||
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[JsonPropertyName("rating")]
|
||||
public RatingResponse Rating { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -30,6 +50,7 @@ public class MovieResponse : Movie
|
||||
ReleaseDate = mediaMovie.Media.ReleaseDate;
|
||||
Length = mediaMovie.Media.Length;
|
||||
Budget = mediaMovie.Budget;
|
||||
Rating = new RatingResponse(mediaMovie.Media.RatingMedia);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
13
WatchIt.Common/WatchIt.Common.Model/Photos/Photo.cs
Normal file
13
WatchIt.Common/WatchIt.Common.Model/Photos/Photo.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Photos;
|
||||
|
||||
public abstract class Photo : Picture
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("background")]
|
||||
public PhotoBackgroundData? Background { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
namespace WatchIt.Common.Model.Photos;
|
||||
|
||||
public class MediaPhotoBackground
|
||||
public class PhotoBackgroundData
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("is_universal_background")]
|
||||
public required bool IsUniversalBackground { get; set; }
|
||||
|
||||
@@ -12,4 +14,6 @@ public class MediaPhotoBackground
|
||||
|
||||
[JsonPropertyName("second_gradient_color")]
|
||||
public required byte[] SecondGradientColor { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Photos;
|
||||
|
||||
public class PhotoBackgroundDataRequest : PhotoBackgroundData
|
||||
{
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public PhotoBackgroundDataRequest() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public PhotoBackgroundDataRequest(PhotoBackgroundData photoBackgroundData)
|
||||
{
|
||||
IsUniversalBackground = photoBackgroundData.IsUniversalBackground;
|
||||
FirstGradientColor = photoBackgroundData.FirstGradientColor;
|
||||
SecondGradientColor = photoBackgroundData.SecondGradientColor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public MediaPhotoImageBackground CreateMediaPhotoImageBackground(Guid photoId) => new MediaPhotoImageBackground
|
||||
{
|
||||
Id = photoId,
|
||||
IsUniversalBackground = IsUniversalBackground,
|
||||
FirstGradientColor = FirstGradientColor,
|
||||
SecondGradientColor = SecondGradientColor,
|
||||
};
|
||||
|
||||
public void UpdateMediaPhotoImageBackground(MediaPhotoImageBackground image)
|
||||
{
|
||||
image.IsUniversalBackground = IsUniversalBackground;
|
||||
image.FirstGradientColor = FirstGradientColor;
|
||||
image.SecondGradientColor = SecondGradientColor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Common.Query;
|
||||
|
||||
namespace WatchIt.Common.Model.Photos;
|
||||
|
||||
public class PhotoQueryParameters : QueryParameters<PhotoResponse>
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[FromQuery(Name = "mime_type")]
|
||||
public string? MimeType { get; set; }
|
||||
|
||||
[FromQuery(Name = "is_background")]
|
||||
public bool? IsBackground { get; set; }
|
||||
|
||||
[FromQuery(Name = "is_universal_background")]
|
||||
public bool? IsUniversalBackground { get; set; }
|
||||
|
||||
[FromQuery(Name = "upload_date")]
|
||||
public DateOnly? UploadDate { get; set; }
|
||||
|
||||
[FromQuery(Name = "upload_date_from")]
|
||||
public DateOnly? UploadDateFrom { get; set; }
|
||||
|
||||
[FromQuery(Name = "upload_date_to")]
|
||||
public DateOnly? UploadDateTo { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public override bool IsMeetingConditions(PhotoResponse item) =>
|
||||
(
|
||||
TestStringWithRegex(item.MimeType, MimeType)
|
||||
&&
|
||||
Test(item.Background is not null, IsBackground)
|
||||
&&
|
||||
Test(item.Background is not null && item.Background.IsUniversalBackground, IsUniversalBackground)
|
||||
&&
|
||||
TestComparable(item.UploadDate, UploadDate, UploadDateFrom, UploadDateTo)
|
||||
);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,16 +1,31 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using WatchIt.Common.Query;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
namespace WatchIt.Common.Model.Photos;
|
||||
|
||||
public class MediaPhotoResponse : MediaPhoto
|
||||
public class PhotoResponse : Photo, IQueryOrderable<PhotoResponse>
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonIgnore]
|
||||
public static IDictionary<string, Func<PhotoResponse, IComparable>> OrderableProperties { get; } = new Dictionary<string, Func<PhotoResponse, IComparable>>
|
||||
{
|
||||
{ "id", x => x.Id },
|
||||
{ "media_id", x => x.MediaId },
|
||||
{ "mime_type", x => x.MimeType },
|
||||
{ "is_background", x => x.Background is not null },
|
||||
{ "is_universal_background", x => x.Background is not null && x.Background.IsUniversalBackground }
|
||||
};
|
||||
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[JsonPropertyName("media_id")]
|
||||
public required long MediaId { get; set; }
|
||||
|
||||
[JsonPropertyName("upload_date")]
|
||||
public DateTime UploadDate { get; set; }
|
||||
|
||||
@@ -21,10 +36,10 @@ public class MediaPhotoResponse : MediaPhoto
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
public MediaPhotoResponse() {}
|
||||
public PhotoResponse() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public MediaPhotoResponse(MediaPhotoImage mediaPhotoImage)
|
||||
public PhotoResponse(MediaPhotoImage mediaPhotoImage)
|
||||
{
|
||||
Id = mediaPhotoImage.Id;
|
||||
MediaId = mediaPhotoImage.MediaId;
|
||||
@@ -34,7 +49,7 @@ public class MediaPhotoResponse : MediaPhoto
|
||||
|
||||
if (mediaPhotoImage.MediaPhotoImageBackground is not null)
|
||||
{
|
||||
Background = new MediaPhotoBackground
|
||||
Background = new PhotoBackgroundData
|
||||
{
|
||||
IsUniversalBackground = mediaPhotoImage.MediaPhotoImageBackground.IsUniversalBackground,
|
||||
FirstGradientColor = mediaPhotoImage.MediaPhotoImageBackground.FirstGradientColor,
|
||||
24
WatchIt.Common/WatchIt.Common.Model/Picture.cs
Normal file
24
WatchIt.Common/WatchIt.Common.Model/Picture.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model;
|
||||
|
||||
public abstract class Picture
|
||||
{
|
||||
#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
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Model.Media;
|
||||
namespace WatchIt.Common.Model.Rating;
|
||||
|
||||
public class MediaRatingRequest
|
||||
public class RatingRequest
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
@@ -17,7 +17,7 @@ public class MediaRatingRequest
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public MediaRatingRequest(short rating)
|
||||
public RatingRequest(short rating)
|
||||
{
|
||||
Rating = rating;
|
||||
}
|
||||
37
WatchIt.Common/WatchIt.Common.Model/Rating/RatingResponse.cs
Normal file
37
WatchIt.Common/WatchIt.Common.Model/Rating/RatingResponse.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using WatchIt.Database.Model.Rating;
|
||||
|
||||
namespace WatchIt.Common.Model.Rating;
|
||||
|
||||
public class RatingResponse
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonPropertyName("average")]
|
||||
public required decimal Average { get; set; }
|
||||
|
||||
[JsonPropertyName("count")]
|
||||
public required long Count { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
[JsonConstructor]
|
||||
public RatingResponse() {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public RatingResponse(IEnumerable<RatingMedia> ratingMedia) : this(ratingMedia.Any() ? (decimal)ratingMedia.Average(x => x.Rating) : 0, ratingMedia.Count()) {}
|
||||
|
||||
[SetsRequiredMembers]
|
||||
public RatingResponse(decimal ratingAverage, long ratingCount)
|
||||
{
|
||||
Average = ratingAverage;
|
||||
Count = ratingCount;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -37,6 +37,24 @@ public class SeriesQueryParameters : QueryParameters<SeriesResponse>
|
||||
[FromQuery(Name = "has_ended")]
|
||||
public bool? HasEnded { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_average")]
|
||||
public decimal? RatingAverage { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_average_from")]
|
||||
public decimal? RatingAverageFrom { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_average_to")]
|
||||
public decimal? RatingAverageTo { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_count")]
|
||||
public long? RatingCount { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_count_from")]
|
||||
public long? RatingCountFrom { get; set; }
|
||||
|
||||
[FromQuery(Name = "rating_count_to")]
|
||||
public long? RatingCountTo { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -45,17 +63,21 @@ public class SeriesQueryParameters : QueryParameters<SeriesResponse>
|
||||
|
||||
public override bool IsMeetingConditions(SeriesResponse item) =>
|
||||
(
|
||||
TestString(item.Title, Title)
|
||||
TestStringWithRegex(item.Title, Title)
|
||||
&&
|
||||
TestString(item.OriginalTitle, OriginalTitle)
|
||||
TestStringWithRegex(item.OriginalTitle, OriginalTitle)
|
||||
&&
|
||||
TestString(item.Description, Description)
|
||||
TestStringWithRegex(item.Description, Description)
|
||||
&&
|
||||
TestComparable(item.ReleaseDate, ReleaseDate, ReleaseDateFrom, ReleaseDateTo)
|
||||
&&
|
||||
TestComparable(item.Length, Length, LengthFrom, LengthTo)
|
||||
&&
|
||||
TestBoolean(item.HasEnded, HasEnded)
|
||||
Test(item.HasEnded, HasEnded)
|
||||
&&
|
||||
TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo)
|
||||
&&
|
||||
TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo)
|
||||
);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.Common.Query;
|
||||
using WatchIt.Database.Model.Media;
|
||||
|
||||
namespace WatchIt.Common.Model.Series;
|
||||
|
||||
public class SeriesResponse : Series
|
||||
public class SeriesResponse : Series, IQueryOrderable<SeriesResponse>
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[JsonIgnore]
|
||||
public static IDictionary<string, Func<SeriesResponse, IComparable>> OrderableProperties { get; } = new Dictionary<string, Func<SeriesResponse, IComparable>>
|
||||
{
|
||||
{ "id", x => x.Id },
|
||||
{ "title", x => x.Title },
|
||||
{ "original_title", x => x.OriginalTitle },
|
||||
{ "description", x => x.Description },
|
||||
{ "release_date", x => x.ReleaseDate },
|
||||
{ "length", x => x.Length },
|
||||
{ "has_ended", x => x.HasEnded },
|
||||
{ "rating.average", x => x.Rating.Average },
|
||||
{ "rating.count", x => x.Rating.Count }
|
||||
};
|
||||
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public long Id { get; set; }
|
||||
|
||||
[JsonPropertyName("rating")]
|
||||
public RatingResponse Rating { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -30,6 +50,7 @@ public class SeriesResponse : Series
|
||||
ReleaseDate = mediaSeries.Media.ReleaseDate;
|
||||
Length = mediaSeries.Media.Length;
|
||||
HasEnded = mediaSeries.HasEnded;
|
||||
Rating = new RatingResponse(mediaSeries.Media.RatingMedia);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
9
WatchIt.Common/WatchIt.Common.Query/IQueryOrderable.cs
Normal file
9
WatchIt.Common/WatchIt.Common.Query/IQueryOrderable.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace WatchIt.Common.Query;
|
||||
|
||||
public interface IQueryOrderable<T>
|
||||
{
|
||||
[JsonIgnore]
|
||||
public static abstract IDictionary<string, Func<T, IComparable>> OrderableProperties { get; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Reflection;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -38,7 +39,12 @@ public abstract class QueryParameters
|
||||
FromQueryAttribute? attribute = property.GetCustomAttributes<FromQueryAttribute>(true).FirstOrDefault();
|
||||
if (value is not null && attribute is not null)
|
||||
{
|
||||
string query = $"{attribute.Name}={value}";
|
||||
string valueString = (value switch
|
||||
{
|
||||
decimal d => d.ToString(CultureInfo.InvariantCulture),
|
||||
_ => value.ToString()
|
||||
})!;
|
||||
string query = $"{attribute.Name}={valueString}";
|
||||
queries.Add(query);
|
||||
}
|
||||
}
|
||||
@@ -52,21 +58,25 @@ public abstract class QueryParameters
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected static bool TestBoolean(bool property, bool? query) =>
|
||||
protected static bool Test<T>(T? property, T? query) =>
|
||||
(
|
||||
query is null
|
||||
||
|
||||
property == query
|
||||
(
|
||||
property is not null
|
||||
&&
|
||||
property.Equals(query)
|
||||
)
|
||||
);
|
||||
|
||||
protected static bool TestString(string? property, string? regexQuery) =>
|
||||
protected static bool TestStringWithRegex(string? property, string? regexQuery) =>
|
||||
(
|
||||
string.IsNullOrEmpty(regexQuery)
|
||||
||
|
||||
(
|
||||
!string.IsNullOrEmpty(property)
|
||||
&&
|
||||
new Regex(regexQuery).IsMatch(property)
|
||||
Regex.IsMatch(property, regexQuery, RegexOptions.IgnoreCase)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -88,7 +98,7 @@ public abstract class QueryParameters
|
||||
(
|
||||
property is not null
|
||||
&&
|
||||
property.CompareTo(from) > 0
|
||||
property.CompareTo(from) >= 0
|
||||
)
|
||||
)
|
||||
&&
|
||||
@@ -108,7 +118,7 @@ public abstract class QueryParameters
|
||||
|
||||
|
||||
|
||||
public abstract class QueryParameters<T> : QueryParameters where T : class
|
||||
public abstract class QueryParameters<T> : QueryParameters where T : IQueryOrderable<T>
|
||||
{
|
||||
#region PUBLIC METHODS
|
||||
|
||||
@@ -120,22 +130,9 @@ public abstract class QueryParameters<T> : QueryParameters where T : class
|
||||
|
||||
if (OrderBy is not null)
|
||||
{
|
||||
PropertyInfo[] properties = typeof(T).GetProperties();
|
||||
foreach (PropertyInfo property in properties)
|
||||
if (T.OrderableProperties.TryGetValue(OrderBy, out Func<T, IComparable>? orderFunc))
|
||||
{
|
||||
JsonPropertyNameAttribute? attribute = property.GetCustomAttributes<JsonPropertyNameAttribute>(true).FirstOrDefault();
|
||||
if (attribute is not null && attribute.Name == OrderBy)
|
||||
{
|
||||
if (Order == "asc")
|
||||
{
|
||||
data = data.OrderBy(property.GetValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = data.OrderByDescending(property.GetValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
data = Order == "asc" ? data.OrderBy(orderFunc) : data.OrderByDescending(orderFunc);
|
||||
}
|
||||
}
|
||||
if (After is not null)
|
||||
|
||||
@@ -4,33 +4,54 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WatchIt.Common.Model.Genres;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.WebAPI.Services.Controllers.Media;
|
||||
|
||||
namespace WatchIt.WebAPI.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("media")]
|
||||
public class MediaController(IMediaControllerService mediaControllerService)
|
||||
public class MediaController : ControllerBase
|
||||
{
|
||||
#region MAIN
|
||||
#region FIELDS
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(MediaResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetMedia([FromRoute] long id) => await mediaControllerService.GetMedia(id);
|
||||
private readonly IMediaControllerService _mediaControllerService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region GENRES
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MediaController(IMediaControllerService mediaControllerService)
|
||||
{
|
||||
_mediaControllerService = mediaControllerService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
#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> GetMediaGenres([FromRoute]long id) => await mediaControllerService.GetMediaGenres(id);
|
||||
public async Task<ActionResult> GetMediaGenres([FromRoute]long id) => await _mediaControllerService.GetMediaGenres(id);
|
||||
|
||||
[HttpPost("{id}/genres/{genre_id}")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
@@ -38,7 +59,7 @@ public class MediaController(IMediaControllerService mediaControllerService)
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> PostMediaGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.PostMediaGenre(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(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
@@ -46,25 +67,23 @@ public class MediaController(IMediaControllerService mediaControllerService)
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> DeleteMediaGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await mediaControllerService.DeleteMediaGenre(id, genreId);
|
||||
public async Task<ActionResult> DeleteMediaGenre([FromRoute]long id, [FromRoute(Name = "genre_id")]short genreId) => await _mediaControllerService.DeleteMediaGenre(id, genreId);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region RATING
|
||||
#region Rating
|
||||
|
||||
[HttpGet("{id}/rating")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(MediaRatingResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(RatingResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetMediaRating([FromRoute] long id) => await mediaControllerService.GetMediaRating(id);
|
||||
public async Task<ActionResult> GetMediaRating([FromRoute] long id) => await _mediaControllerService.GetMediaRating(id);
|
||||
|
||||
[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);
|
||||
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)]
|
||||
@@ -72,38 +91,34 @@ public class MediaController(IMediaControllerService mediaControllerService)
|
||||
[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);
|
||||
public async Task<ActionResult> PutMediaRating([FromRoute] long id, [FromBody] RatingRequest 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);
|
||||
public async Task<ActionResult> DeleteMediaRating([FromRoute] long id) => await _mediaControllerService.DeleteMediaRating(id);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region VIEW COUNT
|
||||
#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);
|
||||
public async Task<ActionResult> PostMediaView([FromRoute] long id) => await _mediaControllerService.PostMediaView(id);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region POSTER
|
||||
#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);
|
||||
public async Task<ActionResult> GetMediaPoster([FromRoute] long id) => await _mediaControllerService.GetMediaPoster(id);
|
||||
|
||||
[HttpPut("{id}/poster")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
@@ -111,68 +126,41 @@ public class MediaController(IMediaControllerService mediaControllerService)
|
||||
[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);
|
||||
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);
|
||||
public async Task<ActionResult> DeleteMediaPoster([FromRoute]long id) => await _mediaControllerService.DeleteMediaPoster(id);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Photos
|
||||
|
||||
|
||||
#region PHOTOS
|
||||
[HttpGet("{id}/photos")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(IEnumerable<PhotoResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetMediaPhotos([FromRoute]long id, PhotoQueryParameters query) => await _mediaControllerService.GetMediaPhotos(id, query);
|
||||
|
||||
[HttpGet("{id}/photos/random_background")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(PhotoResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetMediaPhotoRandomBackground([FromRoute]long id) => await mediaControllerService.GetMediaRandomBackgroundPhoto(id);
|
||||
public async Task<ActionResult> GetMediaPhotoRandomBackground([FromRoute]long id) => await _mediaControllerService.GetMediaPhotoRandomBackground(id);
|
||||
|
||||
[HttpGet("photos/{photo_id}")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetPhoto([FromRoute(Name = "photo_id")] Guid photoId) => await mediaControllerService.GetPhoto(photoId);
|
||||
|
||||
[HttpGet("photos")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(IEnumerable<MediaPhotoResponse>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult> GetPhotos(MediaPhotoQueryParameters query) => await mediaControllerService.GetPhotos(query);
|
||||
|
||||
[HttpGet("photos/random_background")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetPhotoRandomBackground() => await mediaControllerService.GetRandomBackgroundPhoto();
|
||||
|
||||
[HttpPost("photos")]
|
||||
[HttpPost("{id}/photos")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(MediaPhotoResponse), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> PostPhoto([FromBody]MediaPhotoRequest body) => await mediaControllerService.PostPhoto(body);
|
||||
|
||||
[HttpPut("photos/{photo_id}")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(PhotoResponse), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> PutPhoto([FromRoute(Name = "photo_id")]Guid photoId, [FromBody]MediaPhotoRequest body) => await mediaControllerService.PutPhoto(photoId, body);
|
||||
public async Task<ActionResult> PostPhoto([FromRoute]long id, [FromBody]MediaPhotoRequest body) => await _mediaControllerService.PostMediaPhoto(id, body);
|
||||
|
||||
[HttpDelete("photos/{photo_id}")]
|
||||
[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
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.WebAPI.Services.Controllers.Photos;
|
||||
|
||||
namespace WatchIt.WebAPI.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("photos")]
|
||||
public class PhotosController : ControllerBase
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private readonly IPhotosControllerService _photosControllerService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public PhotosController(IPhotosControllerService photosControllerService)
|
||||
{
|
||||
_photosControllerService = photosControllerService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
[HttpGet("random_background")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(PhotoResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> GetPhotoRandomBackground() => await _photosControllerService.GetPhotoRandomBackground();
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[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] Guid id) => await _photosControllerService.DeletePhoto(id);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Background data
|
||||
|
||||
[HttpPut("{id}/background_data")]
|
||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> PutPhotoBackgroundData([FromRoute]Guid id, [FromBody] PhotoBackgroundDataRequest body) => await _photosControllerService.PutPhotoBackgroundData(id, body);
|
||||
|
||||
[HttpDelete("{id}/background_data")]
|
||||
[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> DeletePhotoBackgroundData([FromRoute]Guid id) => await _photosControllerService.DeletePhotoBackgroundData(id);
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -17,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.Photos\WatchIt.WebAPI.Services.Controllers.Photos.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Series\WatchIt.WebAPI.Services.Controllers.Series.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.WebAPI.Services.Controllers.Common;
|
||||
|
||||
namespace WatchIt.WebAPI.Services.Controllers.Media;
|
||||
@@ -13,7 +15,7 @@ public interface IMediaControllerService
|
||||
|
||||
Task<RequestResult> GetMediaRating(long mediaId);
|
||||
Task<RequestResult> GetMediaRatingByUser(long mediaId, long userId);
|
||||
Task<RequestResult> PutMediaRating(long mediaId, MediaRatingRequest data);
|
||||
Task<RequestResult> PutMediaRating(long mediaId, RatingRequest data);
|
||||
Task<RequestResult> DeleteMediaRating(long mediaId);
|
||||
|
||||
Task<RequestResult> PostMediaView(long mediaId);
|
||||
@@ -22,11 +24,7 @@ public interface IMediaControllerService
|
||||
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();
|
||||
Task<RequestResult> GetMediaRandomBackgroundPhoto(long id);
|
||||
Task<RequestResult> PostPhoto(MediaPhotoRequest data);
|
||||
Task<RequestResult> PutPhoto(Guid photoId, MediaPhotoRequest data);
|
||||
Task<RequestResult> DeletePhoto(Guid photoId);
|
||||
Task<RequestResult> GetMediaPhotos(long mediaId, PhotoQueryParameters queryParameters);
|
||||
Task<RequestResult> GetMediaPhotoRandomBackground(long mediaId);
|
||||
Task<RequestResult> PostMediaPhoto(long mediaId, MediaPhotoRequest data);
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
using SimpleToolkit.Extensions;
|
||||
using WatchIt.Common.Model.Genres;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.Database;
|
||||
using WatchIt.Database.Model.Media;
|
||||
using WatchIt.Database.Model.Rating;
|
||||
@@ -106,9 +108,7 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
|
||||
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);
|
||||
RatingResponse ratingResponse = new RatingResponse(item.RatingMedia);
|
||||
|
||||
return RequestResult.Ok(ratingResponse);
|
||||
}
|
||||
@@ -130,7 +130,7 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
|
||||
return RequestResult.Ok(rating.Value);
|
||||
}
|
||||
|
||||
public async Task<RequestResult> PutMediaRating(long mediaId, MediaRatingRequest data)
|
||||
public async Task<RequestResult> PutMediaRating(long mediaId, RatingRequest data)
|
||||
{
|
||||
short ratingValue = data.Rating;
|
||||
if (ratingValue < 1 || ratingValue > 10)
|
||||
@@ -295,50 +295,33 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
|
||||
|
||||
#region Photos
|
||||
|
||||
public async Task<RequestResult> GetPhoto(Guid id)
|
||||
public async Task<RequestResult> GetMediaPhotos(long mediaId, PhotoQueryParameters queryParameters)
|
||||
{
|
||||
MediaPhotoImage? item = await database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (item is null)
|
||||
Database.Model.Media.Media? media = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId);
|
||||
if (media is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
MediaPhotoResponse data = new MediaPhotoResponse(item);
|
||||
return RequestResult.Ok(data);
|
||||
IEnumerable<MediaPhotoImage> imagesRaw = await database.MediaPhotoImages.Where(x => x.MediaId == mediaId).ToListAsync();
|
||||
IEnumerable<PhotoResponse> images = imagesRaw.Select(x => new PhotoResponse(x));
|
||||
images = queryParameters.PrepareData(images);
|
||||
return RequestResult.Ok(images);
|
||||
}
|
||||
|
||||
public async Task<RequestResult> GetPhotos(MediaPhotoQueryParameters query)
|
||||
public Task<RequestResult> GetMediaPhotoRandomBackground(long mediaId)
|
||||
{
|
||||
IEnumerable<MediaPhotoResponse> data = await database.MediaPhotoImages.Select(x => new MediaPhotoResponse(x)).ToListAsync();
|
||||
data = query.PrepareData(data);
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
public Task<RequestResult> GetRandomBackgroundPhoto()
|
||||
{
|
||||
MediaPhotoImage? image = database.MediaPhotoImages.Where(x => x.MediaPhotoImageBackground != null && x.MediaPhotoImageBackground.IsUniversalBackground).Random();
|
||||
MediaPhotoImage? image = database.MediaPhotoImages.Where(x => x.MediaId == mediaId && x.MediaPhotoImageBackground != null).Random();
|
||||
if (image is null)
|
||||
{
|
||||
return Task.FromResult<RequestResult>(RequestResult.NotFound());
|
||||
}
|
||||
|
||||
MediaPhotoResponse data = new MediaPhotoResponse(image);
|
||||
PhotoResponse data = new PhotoResponse(image);
|
||||
return Task.FromResult<RequestResult>(RequestResult.Ok(data));
|
||||
}
|
||||
|
||||
public Task<RequestResult> GetMediaRandomBackgroundPhoto(long id)
|
||||
{
|
||||
MediaPhotoImage? image = database.MediaPhotoImages.Where(x => x.MediaId == id && x.MediaPhotoImageBackground != null).Random();
|
||||
if (image is null)
|
||||
{
|
||||
return Task.FromResult<RequestResult>(RequestResult.NotFound());
|
||||
}
|
||||
|
||||
MediaPhotoResponse data = new MediaPhotoResponse(image);
|
||||
return Task.FromResult<RequestResult>(RequestResult.Ok(data));
|
||||
}
|
||||
|
||||
public async Task<RequestResult> PostPhoto(MediaPhotoRequest data)
|
||||
public async Task<RequestResult> PostMediaPhoto(long mediaId, MediaPhotoRequest data)
|
||||
{
|
||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
@@ -346,7 +329,13 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaPhotoImage item = data.CreateMediaPhotoImage();
|
||||
Database.Model.Media.Media? media = await database.Media.FirstOrDefaultAsync(x => x.Id == mediaId);
|
||||
if (media is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
MediaPhotoImage item = data.CreateMediaPhotoImage(mediaId);
|
||||
await database.MediaPhotoImages.AddAsync(item);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
@@ -357,69 +346,7 @@ public class MediaControllerService(DatabaseContext database, IUserService userS
|
||||
await database.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RequestResult.Created($"photos/{item.Id}", new MediaPhotoResponse(item));
|
||||
}
|
||||
|
||||
public async Task<RequestResult> PutPhoto(Guid photoId, MediaPhotoRequest data)
|
||||
{
|
||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaPhotoImage? item = await database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == photoId);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
data.UpdateMediaPhotoImage(item);
|
||||
if (item.MediaPhotoImageBackground is null && data.Background is not null)
|
||||
{
|
||||
MediaPhotoImageBackground background = data.CreateMediaPhotoImageBackground(item.Id)!;
|
||||
await database.MediaPhotoImageBackgrounds.AddAsync(background);
|
||||
}
|
||||
else if (item.MediaPhotoImageBackground is not null && data.Background is null)
|
||||
{
|
||||
database.MediaPhotoImageBackgrounds.Attach(item.MediaPhotoImageBackground);
|
||||
database.MediaPhotoImageBackgrounds.Remove(item.MediaPhotoImageBackground);
|
||||
}
|
||||
else if (item.MediaPhotoImageBackground is not null && data.Background is not null)
|
||||
{
|
||||
data.UpdateMediaPhotoImageBackground(item.MediaPhotoImageBackground);
|
||||
}
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Ok();
|
||||
}
|
||||
|
||||
public async Task<RequestResult> DeletePhoto(Guid photoId)
|
||||
{
|
||||
UserValidator validator = userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaPhotoImage? item = await database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == photoId);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
if (item.MediaPhotoImageBackground is not null)
|
||||
{
|
||||
database.MediaPhotoImageBackgrounds.Attach(item.MediaPhotoImageBackground);
|
||||
database.MediaPhotoImageBackgrounds.Remove(item.MediaPhotoImageBackground);
|
||||
await database.SaveChangesAsync();
|
||||
}
|
||||
|
||||
database.MediaPhotoImages.Attach(item);
|
||||
database.MediaPhotoImages.Remove(item);
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Ok();
|
||||
return RequestResult.Created($"photos/{item.Id}", new PhotoResponse(item));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -38,7 +38,8 @@ public class MoviesControllerService : IMoviesControllerService
|
||||
|
||||
public async Task<RequestResult> GetAllMovies(MovieQueryParameters query)
|
||||
{
|
||||
IEnumerable<MovieResponse> data = await _database.MediaMovies.Select(x => new MovieResponse(x)).ToListAsync();
|
||||
IEnumerable<MediaMovie> rawData = await _database.MediaMovies.ToListAsync();
|
||||
IEnumerable<MovieResponse> data = rawData.Select(x => new MovieResponse(x));
|
||||
data = query.PrepareData(data);
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.WebAPI.Services.Controllers.Common;
|
||||
|
||||
namespace WatchIt.WebAPI.Services.Controllers.Photos;
|
||||
|
||||
public interface IPhotosControllerService
|
||||
{
|
||||
Task<RequestResult> GetPhotoRandomBackground();
|
||||
Task<RequestResult> DeletePhoto(Guid photoId);
|
||||
|
||||
Task<RequestResult> PutPhotoBackgroundData(Guid id, PhotoBackgroundDataRequest data);
|
||||
Task<RequestResult> DeletePhotoBackgroundData(Guid id);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SimpleToolkit.Extensions;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
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.Photos;
|
||||
|
||||
public class PhotosControllerService : IPhotosControllerService
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private readonly DatabaseContext _database;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONTRUCTORS
|
||||
|
||||
public PhotosControllerService(DatabaseContext database, IUserService userService)
|
||||
{
|
||||
_database = database;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
public Task<RequestResult> GetPhotoRandomBackground()
|
||||
{
|
||||
MediaPhotoImage? image = _database.MediaPhotoImages.Where(x => x.MediaPhotoImageBackground != null && x.MediaPhotoImageBackground.IsUniversalBackground).Random();
|
||||
if (image is null)
|
||||
{
|
||||
return Task.FromResult<RequestResult>(RequestResult.NotFound());
|
||||
}
|
||||
|
||||
PhotoResponse data = new PhotoResponse(image);
|
||||
return Task.FromResult<RequestResult>(RequestResult.Ok(data));
|
||||
}
|
||||
|
||||
public async Task<RequestResult> DeletePhoto(Guid photoId)
|
||||
{
|
||||
UserValidator validator = _userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaPhotoImage? item = await _database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == photoId);
|
||||
if (item is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
if (item.MediaPhotoImageBackground is not null)
|
||||
{
|
||||
_database.MediaPhotoImageBackgrounds.Attach(item.MediaPhotoImageBackground);
|
||||
_database.MediaPhotoImageBackgrounds.Remove(item.MediaPhotoImageBackground);
|
||||
await _database.SaveChangesAsync();
|
||||
}
|
||||
|
||||
_database.MediaPhotoImages.Attach(item);
|
||||
_database.MediaPhotoImages.Remove(item);
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Background data
|
||||
|
||||
public async Task<RequestResult> PutPhotoBackgroundData(Guid id, PhotoBackgroundDataRequest data)
|
||||
{
|
||||
UserValidator validator = _userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaPhotoImage? image = await _database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (image is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
MediaPhotoImageBackground? imageBackground = image.MediaPhotoImageBackground;
|
||||
if (imageBackground is null)
|
||||
{
|
||||
imageBackground = data.CreateMediaPhotoImageBackground(id);
|
||||
await _database.MediaPhotoImageBackgrounds.AddAsync(imageBackground);
|
||||
}
|
||||
else
|
||||
{
|
||||
data.UpdateMediaPhotoImageBackground(imageBackground);
|
||||
}
|
||||
await _database.SaveChangesAsync();
|
||||
|
||||
return RequestResult.Ok();
|
||||
}
|
||||
|
||||
public async Task<RequestResult> DeletePhotoBackgroundData(Guid id)
|
||||
{
|
||||
UserValidator validator = _userService.GetValidator().MustBeAdmin();
|
||||
if (!validator.IsValid)
|
||||
{
|
||||
return RequestResult.Forbidden();
|
||||
}
|
||||
|
||||
MediaPhotoImage? image = await _database.MediaPhotoImages.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (image is null)
|
||||
{
|
||||
return RequestResult.NotFound();
|
||||
}
|
||||
|
||||
MediaPhotoImageBackground? imageBackground = image.MediaPhotoImageBackground;
|
||||
if (imageBackground is not null)
|
||||
{
|
||||
_database.MediaPhotoImageBackgrounds.Attach(imageBackground);
|
||||
_database.MediaPhotoImageBackgrounds.Remove(imageBackground);
|
||||
await _database.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RequestResult.Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SimpleToolkit.Extensions" Version="1.7.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -38,7 +38,8 @@ public class SeriesControllerService : ISeriesControllerService
|
||||
|
||||
public async Task<RequestResult> GetAllSeries(SeriesQueryParameters query)
|
||||
{
|
||||
IEnumerable<SeriesResponse> data = await _database.MediaSeries.Select(x => new SeriesResponse(x)).ToListAsync();
|
||||
IEnumerable<MediaSeries> rawData = await _database.MediaSeries.ToListAsync();
|
||||
IEnumerable<SeriesResponse> data = rawData.Select(x => new SeriesResponse(x));
|
||||
data = query.PrepareData(data);
|
||||
return RequestResult.Ok(data);
|
||||
}
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Database;
|
||||
using WatchIt.WebAPI.Validators.Photos;
|
||||
|
||||
namespace WatchIt.WebAPI.Validators.Media;
|
||||
|
||||
public class MediaPhotoRequestValidator : AbstractValidator<MediaPhotoRequest>
|
||||
{
|
||||
public MediaPhotoRequestValidator(DatabaseContext database)
|
||||
public MediaPhotoRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.MediaId).MustBeIn(database.Media, x => x.Id).WithMessage("Media does not exists");
|
||||
RuleFor(x => x.Image).NotEmpty();
|
||||
RuleFor(x => x.MimeType).Matches(@"\w+/.+").WithMessage("Incorrect mimetype");
|
||||
When(x => x.Background is not null, () =>
|
||||
{
|
||||
RuleFor(x => x.Background!.FirstGradientColor).Must(x => x.Length == 3).WithMessage("First gradient color has to be 3 byte long");
|
||||
RuleFor(x => x.Background!.SecondGradientColor).Must(x => x.Length == 3).WithMessage("Second gradient color has to be 3 byte long");
|
||||
RuleFor(x => x.Background!).SetValidator(new PhotoBackgroundDataValidator());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using FluentValidation;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
|
||||
namespace WatchIt.WebAPI.Validators.Photos;
|
||||
|
||||
public class PhotoBackgroundDataRequestValidator : AbstractValidator<PhotoBackgroundDataRequest>
|
||||
{
|
||||
public PhotoBackgroundDataRequestValidator()
|
||||
{
|
||||
RuleFor(x => x).SetValidator(new PhotoBackgroundDataValidator());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using FluentValidation;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
|
||||
namespace WatchIt.WebAPI.Validators.Photos;
|
||||
|
||||
public class PhotoBackgroundDataValidator : AbstractValidator<PhotoBackgroundData>
|
||||
{
|
||||
public PhotoBackgroundDataValidator()
|
||||
{
|
||||
RuleFor(x => x.FirstGradientColor).Must(x => x.Length == 3).WithMessage("First gradient color has to be 3 byte long");
|
||||
RuleFor(x => x.SecondGradientColor).Must(x => x.Length == 3).WithMessage("Second gradient color has to be 3 byte long");
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ 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.Photos;
|
||||
using WatchIt.WebAPI.Services.Controllers.Series;
|
||||
using WatchIt.WebAPI.Services.Utility.Configuration;
|
||||
using WatchIt.WebAPI.Services.Utility.Tokens;
|
||||
@@ -156,6 +157,7 @@ public static class Program
|
||||
builder.Services.AddTransient<IMoviesControllerService, MoviesControllerService>();
|
||||
builder.Services.AddTransient<IMediaControllerService, MediaControllerService>();
|
||||
builder.Services.AddTransient<ISeriesControllerService, SeriesControllerService>();
|
||||
builder.Services.AddTransient<IPhotosControllerService, PhotosControllerService>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ public class Endpoints
|
||||
public Media Media { get; set; }
|
||||
public Movies Movies { get; set; }
|
||||
public Series Series { get; set; }
|
||||
public Photos Photos { get; set; }
|
||||
}
|
||||
@@ -3,23 +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 GetMedia { get; set; }
|
||||
public string GetMediaGenres { get; set; }
|
||||
public string PostMediaGenre { get; set; }
|
||||
public string DeleteMediaGenre { 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; }
|
||||
public string PostPhoto { get; set; }
|
||||
public string PutPhoto { get; set; }
|
||||
public string DeletePhoto { get; set; }
|
||||
public string GetMediaPoster { get; set; }
|
||||
public string PutMediaPoster { get; set; }
|
||||
public string DeleteMediaPoster { get; set; }
|
||||
public string GetMediaPhotos { get; set; }
|
||||
public string GetMediaPhotoRandomBackground { get; set; }
|
||||
public string PostMediaPhoto { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace WatchIt.Website.Services.Utility.Configuration.Model;
|
||||
|
||||
public class Photos
|
||||
{
|
||||
public string Base { get; set; }
|
||||
public string GetPhotoRandomBackground { get; set; }
|
||||
public string DeletePhoto { get; set; }
|
||||
public string PutPhotoBackgroundData { get; set; }
|
||||
public string DeletePhotoBackgroundData { get; set; }
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using WatchIt.Common.Model.Genres;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Media;
|
||||
|
||||
@@ -9,17 +11,20 @@ public interface IMediaWebAPIService
|
||||
|
||||
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 DeleteMediaGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
|
||||
|
||||
Task GetMediaRating(long mediaId, Action<MediaRatingResponse>? successAction = null, Action? notFoundAction = null);
|
||||
Task GetMediaRating(long mediaId, Action<RatingResponse>? successAction = null, Action? notFoundAction = null);
|
||||
Task GetMediaRatingByUser(long mediaId, long userId, Action<short>? successAction = null, Action? notFoundAction = null);
|
||||
Task PutMediaRating(long mediaId, MediaRatingRequest body, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null);
|
||||
Task PutMediaRating(long mediaId, RatingRequest body, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null);
|
||||
Task DeleteMediaRating(long mediaId, Action? successAction = null, Action? unauthorizedAction = null);
|
||||
|
||||
Task 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);
|
||||
Task GetMediaPoster(long mediaId, Action<MediaPosterResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
|
||||
Task PutMediaPoster(long mediaId, MediaPosterRequest data, Action<MediaPosterResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task DeleteMediaPoster(long mediaId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
|
||||
Task GetMediaPhotos(long mediaId, PhotoQueryParameters? query = null, Action<IEnumerable<PhotoResponse>>? successAction = null, Action? notFoundAction = null);
|
||||
Task GetMediaPhotoRandomBackground(long mediaId, Action<PhotoResponse>? successAction = null, Action? notFoundAction = null);
|
||||
Task PostMediaPhoto(long mediaId, MediaPhotoRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using WatchIt.Common.Model.Genres;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.Utility.Configuration.Model;
|
||||
@@ -7,19 +9,38 @@ using WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Media;
|
||||
|
||||
public class MediaWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : BaseWebAPIService(configurationService), IMediaWebAPIService
|
||||
public class MediaWebAPIService : BaseWebAPIService, IMediaWebAPIService
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private readonly IHttpClientService _httpClientService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MediaWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : base(configurationService)
|
||||
{
|
||||
_httpClientService = httpClientService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
public async Task GetMedia(long mediaId, Action<MediaResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.Get, mediaId);
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetMedia, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
@@ -31,11 +52,11 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
|
||||
public async Task GetMediaGenres(long mediaId, Action<IEnumerable<GenreResponse>>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetGenres, mediaId);
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetMediaGenres, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
@@ -43,11 +64,25 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
|
||||
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);
|
||||
string url = GetUrl(EndpointsConfiguration.Media.PostMediaGenre, mediaId, genreId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task DeleteMediaGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.DeleteMediaGenre, mediaId, genreId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
@@ -59,13 +94,13 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
|
||||
#region Rating
|
||||
|
||||
public async Task GetMediaRating(long mediaId, Action<MediaRatingResponse>? successAction = null, Action? notFoundAction = null)
|
||||
public async Task GetMediaRating(long mediaId, Action<RatingResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetMediaRating, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
@@ -77,13 +112,13 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
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)
|
||||
public async Task PutMediaRating(long mediaId, RatingRequest body, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.PutMediaRating, mediaId);
|
||||
|
||||
@@ -92,7 +127,7 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
Body = body
|
||||
};
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
@@ -106,7 +141,7 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.ExecuteAction();
|
||||
@@ -122,7 +157,7 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
@@ -130,55 +165,31 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
|
||||
#endregion
|
||||
|
||||
#region Poster
|
||||
|
||||
|
||||
public async Task GetPhotoMediaRandomBackground(long mediaId, Action<MediaPhotoResponse>? successAction = null, Action? notFoundAction = null)
|
||||
public async Task GetMediaPoster(long mediaId, Action<MediaPosterResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetPhotoMediaRandomBackground, mediaId);
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetMediaPoster, 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);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.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);
|
||||
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)
|
||||
public async Task PutMediaPoster(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);
|
||||
string url = GetUrl(EndpointsConfiguration.Media.PutMediaPoster, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Put, url)
|
||||
{
|
||||
Body = data
|
||||
};
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
@@ -186,13 +197,13 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task DeletePoster(long mediaId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
public async Task DeleteMediaPoster(long mediaId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.DeletePoster, mediaId);
|
||||
string url = GetUrl(EndpointsConfiguration.Media.DeleteMediaPoster, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
@@ -201,6 +212,54 @@ public class MediaWebAPIService(IHttpClientService httpClientService, IConfigura
|
||||
|
||||
#endregion
|
||||
|
||||
#region Photos
|
||||
|
||||
public async Task GetMediaPhotos(long mediaId, PhotoQueryParameters? query = null, Action<IEnumerable<PhotoResponse>>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetMediaPhotos, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task GetMediaPhotoRandomBackground(long mediaId, Action<PhotoResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.GetMediaPhotoRandomBackground, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task PostMediaPhoto(long mediaId, MediaPhotoRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Media.PostMediaPhoto, mediaId);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
|
||||
{
|
||||
Body = data
|
||||
};
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using WatchIt.Common.Model.Photos;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Photos;
|
||||
|
||||
public interface IPhotosWebAPIService
|
||||
{
|
||||
Task GetPhotoRandomBackground(Action<PhotoResponse>? successAction = null, Action? notFoundAction = null);
|
||||
Task DeletePhoto(Guid id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
|
||||
Task PutPhotoBackgroundData(Guid id, PhotoBackgroundDataRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
|
||||
Task DeletePhotoBackgroundData(Guid id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Photos;
|
||||
|
||||
public class PhotosWebAPIService : BaseWebAPIService, IPhotosWebAPIService
|
||||
{
|
||||
#region FIELDS
|
||||
|
||||
private readonly IHttpClientService _httpClientService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public PhotosWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : base(configurationService)
|
||||
{
|
||||
_httpClientService = httpClientService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
public async Task GetPhotoRandomBackground(Action<PhotoResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Photos.GetPhotoRandomBackground);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task DeletePhoto(Guid id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Photos.DeletePhoto, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Background data
|
||||
|
||||
public async Task PutPhotoBackgroundData(Guid id, PhotoBackgroundDataRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Photos.PutPhotoBackgroundData, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Put, url)
|
||||
{
|
||||
Body = data
|
||||
};
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task DeletePhotoBackgroundData(Guid id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Photos.DeletePhotoBackgroundData, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override string GetServiceBase() => EndpointsConfiguration.Photos.Base;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<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.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>
|
||||
@@ -9,13 +9,20 @@
|
||||
<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"/>
|
||||
<link rel="stylesheet" href="css/general.css?version=0.2.0.3"/>
|
||||
<link rel="stylesheet" href="css/main_button.css?version=0.2.0.0"/>
|
||||
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.2.0.12"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
|
||||
<!-- 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">
|
||||
|
||||
<!-- BLAZORISE -->
|
||||
<link href="_content/Blazorise.Icons.FontAwesome/v6/css/all.min.css" rel="stylesheet">
|
||||
<link href="_content/Blazorise/blazorise.css" rel="stylesheet" />
|
||||
<link href="_content/Blazorise.Bootstrap5/blazorise.bootstrap5.css" rel="stylesheet" />
|
||||
|
||||
<!-- FONTS -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
@typeparam TItem where TItem : WatchIt.Common.Query.IQueryOrderable<TItem>
|
||||
@typeparam TQuery where TQuery : WatchIt.Common.Query.QueryParameters<TItem>
|
||||
|
||||
|
||||
|
||||
<CascadingValue Value="this">
|
||||
<div class="vstack gap-3">
|
||||
<div class="rounded-3 panel panel-regular p-2 px-3 z-3">
|
||||
<div class="container-grid">
|
||||
<div class="row gx-3">
|
||||
<div class="col">
|
||||
<h2 class="m-0">@(Title)</h2>
|
||||
</div>
|
||||
<div class="col-auto align-self-center">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">Order by</span>
|
||||
<select class="form-select" @onchange="SortingOptionChanged">
|
||||
@foreach (KeyValuePair<string, string> sortingOption in SortingOptions)
|
||||
{
|
||||
<option value="@(sortingOption.Key)">@(sortingOption.Value)</option>
|
||||
}
|
||||
</select>
|
||||
<input type="checkbox" class="btn-check" id="sortingAscending" autocomplete="off" @onchange="SortingAscendingChanged">
|
||||
<label class="btn btn-outline-secondary" for="sortingAscending">↓︎</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto align-self-center">
|
||||
<div class="d-flex">
|
||||
<Dropdown RightAligned>
|
||||
<DropdownToggle Color="Color.Secondary" Size="Size.Small">
|
||||
<i class="fa fa-filter"></i>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu Class="p-2">
|
||||
<DropdownHeader>Filters</DropdownHeader>
|
||||
<DropdownDivider/>
|
||||
@(ChildContent)
|
||||
<DropdownDivider/>
|
||||
<button class="btn btn-secondary btn-sm w-100" @onclick="FilterApplied">Apply</button>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error))
|
||||
{
|
||||
foreach (TItem item in _items)
|
||||
{
|
||||
<div role="button" class="rounded-3 panel panel-regular p-2" @onclick="@(() => NavigationManager.NavigateTo(string.Format(UrlIdTemplate, IdSource(item))))">
|
||||
<ListItemComponent Id="@(IdSource(item))"
|
||||
Name="@(NameSource(item))"
|
||||
AdditionalNameInfo="@(AdditionalNameInfoSource(item))"
|
||||
Rating="@(RatingSource(item))"
|
||||
PictureDownloadingTask="@(PictureDownloadingTask)"/>
|
||||
</div>
|
||||
}
|
||||
if (!_allItemsLoaded)
|
||||
{
|
||||
<div role="button" class="rounded-3 panel panel-regular p-3" @onclick="DownloadItems">
|
||||
<div class="d-flex justify-content-center">
|
||||
@if (!_itemsLoading)
|
||||
{
|
||||
<strong>Load more</strong>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<strong>Loading...</strong>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<ErrorComponent ErrorMessage="@_error"/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="m-5">
|
||||
<LoadingComponent/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</CascadingValue>
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.Common.Query;
|
||||
|
||||
namespace WatchIt.Website.Components.DatabasePage;
|
||||
|
||||
public partial class DatabasePageComponent<TItem, TQuery> : ComponentBase where TItem : IQueryOrderable<TItem> where TQuery : QueryParameters<TItem>
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter] public required string Title { get; set; }
|
||||
[Parameter] public required Func<TItem, long> IdSource { get; set; }
|
||||
[Parameter] public required Func<TItem, string> NameSource { get; set; }
|
||||
[Parameter] public Func<TItem, string?> AdditionalNameInfoSource { get; set; } = _ => null;
|
||||
[Parameter] public required Func<TItem, RatingResponse> RatingSource { get; set; }
|
||||
[Parameter] public required string UrlIdTemplate { get; set; }
|
||||
[Parameter] public required Func<long, Action<Picture>, Task> PictureDownloadingTask { get; set; }
|
||||
[Parameter] public required Func<TQuery, Action<IEnumerable<TItem>>, Task> ItemDownloadingTask { get; set; }
|
||||
[Parameter] public required Dictionary<string, string> SortingOptions { get; set; }
|
||||
[Parameter] public required RenderFragment ChildContent { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded;
|
||||
private string? _error;
|
||||
|
||||
private List<TItem> _items = new List<TItem>();
|
||||
private bool _allItemsLoaded;
|
||||
private bool _itemsLoading;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public TQuery Query { get; set; } = Activator.CreateInstance<TQuery>()!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// INIT
|
||||
Query.OrderBy = SortingOptions.Keys.First();
|
||||
Query.First = 100;
|
||||
|
||||
List<Task> endTasks = new List<Task>();
|
||||
|
||||
// STEP 0
|
||||
endTasks.AddRange(
|
||||
[
|
||||
ItemDownloadingTask(Query, data =>
|
||||
{
|
||||
_items.AddRange(data);
|
||||
if (data.Count() < 100)
|
||||
{
|
||||
_allItemsLoaded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Query.After = 100;
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
// END
|
||||
await Task.WhenAll(endTasks);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadItems()
|
||||
{
|
||||
_itemsLoading = true;
|
||||
await ItemDownloadingTask(Query, AppendNewItems);
|
||||
}
|
||||
|
||||
private async Task SortingAscendingChanged(ChangeEventArgs args)
|
||||
{
|
||||
Query.Order = (bool)args.Value! ? "asc" : "desc";
|
||||
await UpdateItems();
|
||||
}
|
||||
|
||||
private async Task SortingOptionChanged(ChangeEventArgs args)
|
||||
{
|
||||
Query.OrderBy = args.Value!.ToString();
|
||||
await UpdateItems();
|
||||
}
|
||||
|
||||
private async Task FilterApplied() => await UpdateItems();
|
||||
|
||||
private async Task UpdateItems()
|
||||
{
|
||||
_loaded = false;
|
||||
Query.First = 100;
|
||||
Query.After = null;
|
||||
_items.Clear();
|
||||
await ItemDownloadingTask(Query, AppendNewItems);
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
private void AppendNewItems(IEnumerable<TItem> items)
|
||||
{
|
||||
_items.AddRange(items);
|
||||
if (items.Count() < 100)
|
||||
{
|
||||
_allItemsLoaded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Query.After += 100;
|
||||
}
|
||||
_itemsLoading = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Query;
|
||||
|
||||
namespace WatchIt.Website.Components.DatabasePage;
|
||||
|
||||
public abstract class FilterFormComponent<TItem, TQuery> : ComponentBase where TItem : IQueryOrderable<TItem> where TQuery : QueryParameters<TItem>
|
||||
{
|
||||
#region PARAMETERS
|
||||
|
||||
[CascadingParameter]
|
||||
protected DatabasePageComponent<TItem, TQuery> Parent { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
protected TQuery? Query => Parent?.Query;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
@inherits FilterFormComponent<WatchIt.Common.Model.Movies.MovieResponse, WatchIt.Common.Model.Movies.MovieQueryParameters>
|
||||
|
||||
|
||||
|
||||
<EditForm Model="@(Query)">
|
||||
<div class="container-grid">
|
||||
<div class="row mb-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Title</span>
|
||||
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Title)"></InputText>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Original title</span>
|
||||
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.OriginalTitle)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Description</span>
|
||||
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Description)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Release date</span>
|
||||
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateFrom)"/>
|
||||
<span class="col-auto input-group-text">-</span>
|
||||
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateTo)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Length</span>
|
||||
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthFrom)"/>
|
||||
<span class="col-auto input-group-text">-</span>
|
||||
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthTo)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Budget</span>
|
||||
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" @bind-Value="@(Query.BudgetFrom)"/>
|
||||
<span class="col-auto input-group-text">-</span>
|
||||
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" @bind-Value="@(Query.BudgetTo)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Rating (count)</span>
|
||||
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountFrom)"/>
|
||||
<span class="col-auto input-group-text">-</span>
|
||||
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountTo)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Rating (average)</span>
|
||||
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageFrom)"/>
|
||||
<span class="col-auto input-group-text">-</span>
|
||||
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageTo)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
@@ -0,0 +1,69 @@
|
||||
@inherits FilterFormComponent<WatchIt.Common.Model.Series.SeriesResponse, WatchIt.Common.Model.Series.SeriesQueryParameters>
|
||||
|
||||
<EditForm Model="@(Query)">
|
||||
<div class="container-grid">
|
||||
<div class="row mb-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Title</span>
|
||||
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Title)"></InputText>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Original title</span>
|
||||
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.OriginalTitle)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Description</span>
|
||||
<InputText class="col form-control" placeholder="Search with regex" @bind-Value="@(Query.Description)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Release date</span>
|
||||
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateFrom)"/>
|
||||
<span class="col-auto input-group-text">-</span>
|
||||
<InputDate TValue="DateOnly?" class="col form-control" @bind-Value="@(Query.ReleaseDateTo)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Length</span>
|
||||
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthFrom)"/>
|
||||
<span class="col-auto input-group-text">-</span>
|
||||
<NumericEdit TValue="short?" Class="col form-control" Min="0" @bind-Value="@(Query.LengthTo)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Has ended</span>
|
||||
<div class="btn-group col">
|
||||
<input type="radio" class="btn-check" name="has_ended" id="has_ended_yes" autocomplete="off" @onclick="() => Query.HasEnded = true">
|
||||
<label class="btn btn-outline-secondary btn-sm" for="has_ended_yes">Yes</label>
|
||||
<input type="radio" class="btn-check" name="has_ended" id="has_ended_no_choice" autocomplete="off" @onclick="() => Query.HasEnded = null" checked>
|
||||
<label class="btn btn-outline-secondary btn-sm" for="has_ended_no_choice">No choice</label>
|
||||
<input type="radio" class="btn-check" name="has_ended" id="has_ended_no" autocomplete="off" @onclick="() => Query.HasEnded = false">
|
||||
<label class="btn btn-outline-secondary btn-sm" for="has_ended_no">No</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Rating (count)</span>
|
||||
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountFrom)"/>
|
||||
<span class="col-auto input-group-text">-</span>
|
||||
<NumericEdit TValue="long?" Class="col form-control" Min="0" @bind-Value="@(Query.RatingCountTo)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-1">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="col-3 input-group-text">Rating (average)</span>
|
||||
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageFrom)"/>
|
||||
<span class="col-auto input-group-text">-</span>
|
||||
<NumericEdit TValue="decimal?" Class="col form-control" Min="0" Max="10" Step="@(0.01M)" @bind-Value="@(Query.RatingAverageTo)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
@@ -1,7 +1,5 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-4">
|
||||
<div class="container-fluid">
|
||||
<div class="container-grid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-center">
|
||||
@@ -28,5 +26,3 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="container-grid">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<img id="picture" class="rounded-2 shadow object-fit-cover picture-aspect-ratio" src="@(_picture is not null ? _picture.ToString() : "assets/poster.png")" alt="picture" height="@(PictureHeight)"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-start flex-column h-100">
|
||||
<div class="mb-auto">
|
||||
<span id="nameText">
|
||||
<strong>@(Name)</strong>@(string.IsNullOrWhiteSpace(AdditionalNameInfo) ? string.Empty : AdditionalNameInfo)
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-inline-flex gap-2">
|
||||
<span id="ratingStar">★</span>
|
||||
<div class="d-inline-flex flex-column justify-content-center">
|
||||
<span id="ratingValue">@(Rating.Count > 0 ? Rating.Average : "--")/10</span>
|
||||
@if (Rating.Count > 0)
|
||||
{
|
||||
<span id="ratingCount">@(Rating.Count)</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,54 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
|
||||
namespace WatchIt.Website.Components;
|
||||
|
||||
public partial class ListItemComponent : ComponentBase
|
||||
{
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter] public required long Id { get; set; }
|
||||
[Parameter] public required string Name { get; set; }
|
||||
[Parameter] public string? AdditionalNameInfo { get; set; }
|
||||
[Parameter] public required RatingResponse Rating { get; set; }
|
||||
[Parameter] public required Func<long, Action<Picture>, Task> PictureDownloadingTask { get; set; }
|
||||
[Parameter] public int PictureHeight { get; set; } = 150;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded;
|
||||
|
||||
private Picture? _picture;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
List<Task> endTasks = new List<Task>();
|
||||
|
||||
// STEP 0
|
||||
endTasks.AddRange(
|
||||
[
|
||||
PictureDownloadingTask(Id, picture => _picture = picture),
|
||||
]);
|
||||
|
||||
await Task.WhenAll(endTasks);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* IDS */
|
||||
|
||||
#nameText {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
#ratingStar {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
#ratingValue {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#ratingCount {
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex flex-column m-5">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="d-flex justify-content-center">
|
||||
<div id="spinner" class="spinner-border text-dark"></div>
|
||||
<div id="spinner" class="spinner-border text-@(Color)"></div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<p id="text" class="text-dark">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
<p id="text" class="text-@(Color)" m-0>Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace WatchIt.Website.Components;
|
||||
|
||||
public partial class LoadingComponent : ComponentBase
|
||||
{
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter] public string Color { get; set; } = "dark";
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
@using Microsoft.IdentityModel.Tokens
|
||||
|
||||
@typeparam TItem
|
||||
@typeparam TQuery where TQuery : WatchIt.Common.Query.QueryParameters
|
||||
|
||||
|
||||
|
||||
<div class="rounded-3 panel panel-regular p-4">
|
||||
<div class="container-grid">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h4 class="m-0"><strong>@(Title)</strong></h4>
|
||||
</div>
|
||||
</div>
|
||||
@if (_loaded)
|
||||
{
|
||||
if (!_items.IsNullOrEmpty())
|
||||
{
|
||||
for (int i = 0; i < _items.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a class="text-reset text-decoration-none" href="@(string.Format(UrlIdTemplate, IdSource(_items[i])))">
|
||||
<ListItemComponent Id="@(IdSource(_items[i]))"
|
||||
Name="@(NameSource(_items[i]))"
|
||||
AdditionalNameInfo="@(AdditionalNameInfoSource(_items[i]))"
|
||||
Rating="@(RatingSource(_items[i]))"
|
||||
PictureDownloadingTask="@(PictureDownloadingTask)"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
if (!_allItemsLoaded)
|
||||
{
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-center">
|
||||
<button class="btn btn-secondary" @onclick="DownloadItems">
|
||||
@if (!_itemsLoading)
|
||||
{
|
||||
<span>Load more</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span>Loading...</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-center">
|
||||
No items found
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<LoadingComponent Color="light"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,93 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.Common.Query;
|
||||
|
||||
namespace WatchIt.Website.Components.SearchPage;
|
||||
|
||||
public partial class SearchResultComponent<TItem, TQuery> : ComponentBase where TQuery : QueryParameters
|
||||
{
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter] public required string Title { get; set; }
|
||||
[Parameter] public required TQuery Query { get; set; }
|
||||
[Parameter] public required Func<TItem, long> IdSource { get; set; }
|
||||
[Parameter] public required Func<TItem, string> NameSource { get; set; }
|
||||
[Parameter] public Func<TItem, string?> AdditionalNameInfoSource { get; set; } = _ => null;
|
||||
[Parameter] public required Func<TItem, RatingResponse> RatingSource { get; set; }
|
||||
[Parameter] public required string UrlIdTemplate { get; set; }
|
||||
[Parameter] public required Func<TQuery, Action<IEnumerable<TItem>>, Task> ItemDownloadingTask { get; set; }
|
||||
[Parameter] public required Func<long, Action<Picture>, Task> PictureDownloadingTask { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded;
|
||||
|
||||
private List<TItem> _items = [];
|
||||
private bool _allItemsLoaded;
|
||||
private bool _itemsLoading;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// INIT
|
||||
Query.First = 5;
|
||||
|
||||
List<Task> endTasks = new List<Task>();
|
||||
|
||||
// STEP 0
|
||||
endTasks.AddRange(
|
||||
[
|
||||
ItemDownloadingTask(Query, data =>
|
||||
{
|
||||
_items.AddRange(data);
|
||||
if (data.Count() < 5)
|
||||
{
|
||||
_allItemsLoaded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Query.After = 5;
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
// END
|
||||
await Task.WhenAll(endTasks);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadItems()
|
||||
{
|
||||
_itemsLoading = true;
|
||||
await ItemDownloadingTask(Query, data =>
|
||||
{
|
||||
_items.AddRange(data);
|
||||
if (data.Count() < 5)
|
||||
{
|
||||
_allItemsLoaded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Query.After += 5;
|
||||
}
|
||||
_itemsLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,175 +1,102 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using System.Net
|
||||
@using WatchIt.Common.Model.Photos
|
||||
@using WatchIt.Website.Services.WebAPI.Photos
|
||||
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
|
||||
|
||||
<CascadingValue Value="this">
|
||||
@if (_loaded)
|
||||
{
|
||||
<div class="container-xl">
|
||||
<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="/">
|
||||
<div class="row sticky-top top-3 mb-2rem">
|
||||
<div class="col">
|
||||
<div class="panel panel-header rounded-3 px-3">
|
||||
<div class="container-grid">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<a id="logo" class="logo" href="/">
|
||||
WatchIt
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<p>Menu</p>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="d-flex flex-row-reverse">
|
||||
@if (_user is null)
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
@if (_searchbarVisible)
|
||||
{
|
||||
<a class="main-button" href="/auth">Sign in</a>
|
||||
<div class="input-group input-group-sm">
|
||||
<InputText class="form-control" placeholder="Search with regex" @bind-Value="@(_searchbarText)"/>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="@(SearchStart)">⌕</button>
|
||||
<button type="button" class="btn btn-sm" @onclick="@(() => _searchbarVisible = false)">❌︎</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<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>
|
||||
<Dropdown>
|
||||
<DropdownToggle Color="Color.Default" Size="Size.Small" ToggleIconVisible="false">Database</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
<DropdownItem Clicked="@(() => NavigationManager.NavigateTo("/database/movies"))">Movies</DropdownItem>
|
||||
<DropdownItem Clicked="@(() => NavigationManager.NavigateTo("/database/series"))">TV Series</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<button type="button" class="btn btn-sm" @onclick="@(() => _searchbarVisible = true)">⌕</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="float-end">
|
||||
@if (_user is null)
|
||||
{
|
||||
<a id="signInButton" class="main-button" href="/auth?redirect_to=@(WebUtility.UrlEncode(NavigationManager.Uri))">Sign in</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Dropdown RightAligned>
|
||||
<Button Color="Color.Default">
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<img class="rounded-circle" alt="avatar" height="30" src="@(_userProfilePicture is null ? "assets/user_placeholder.png" : _userProfilePicture.ToString())"/>
|
||||
<span>@(_user.Username)</span>
|
||||
</div>
|
||||
</Button>
|
||||
<DropdownToggle Color="Color.Default" Split />
|
||||
<DropdownMenu >
|
||||
@if (_user.IsAdmin)
|
||||
{
|
||||
<a class="dropdown-item" href="/admin">Administrator panel</a>
|
||||
<DropdownItem Clicked="@(() => NavigationManager.NavigateTo("/admin"))">Administrator panel</DropdownItem>
|
||||
}
|
||||
<DropdownDivider/>
|
||||
<DropdownItem Clicked="UserMenuLogOut"><span class="text-danger">Log out</span></DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
<div class="dropdown-menu-separator"></div>
|
||||
<a class="dropdown-item text-danger" @onclick="UserMenuLogOut">Log out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col z-0 p-1">
|
||||
<div class="col">
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* TAGS */
|
||||
body {
|
||||
background-image: url('@_background');
|
||||
|
||||
height: 100%;
|
||||
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
background-image: url('@(GetBackgroundPhoto() is null ? "assets/background_temp.jpg": GetBackgroundPhoto().ToString())');
|
||||
}
|
||||
|
||||
.logo, .main-button {
|
||||
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
||||
}
|
||||
|
||||
#user-menu {
|
||||
display: @(_userMenuIsActive ? "block" : "none");
|
||||
position: fixed;
|
||||
/* IDS */
|
||||
|
||||
#logo, #signInButton {
|
||||
background-image: linear-gradient(45deg, @(GetBackgroundPhoto() is null ? "#c6721c, #85200c" : $"#{Convert.ToHexString(GetBackgroundPhoto().Background.FirstGradientColor)}, #{Convert.ToHexString(GetBackgroundPhoto().Background.SecondGradientColor)}"));
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@code
|
||||
{
|
||||
#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
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded = 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
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
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) =>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
</CascadingValue>
|
||||
140
WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs
Normal file
140
WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model.Accounts;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Photos;
|
||||
|
||||
namespace WatchIt.Website.Layout;
|
||||
|
||||
public partial class MainLayout : LayoutComponentBase
|
||||
{
|
||||
#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!;
|
||||
[Inject] public IPhotosWebAPIService PhotosWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded;
|
||||
|
||||
private User? _user;
|
||||
private PhotoResponse? _defaultBackgroundPhoto;
|
||||
private AccountProfilePictureResponse? _userProfilePicture;
|
||||
|
||||
private bool _searchbarVisible;
|
||||
private string _searchbarText = string.Empty;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
private PhotoResponse? _backgroundPhoto;
|
||||
public PhotoResponse? BackgroundPhoto
|
||||
{
|
||||
get => _backgroundPhoto;
|
||||
set
|
||||
{
|
||||
_backgroundPhoto = value;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
#region Main
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
List<Task> endTasks = new List<Task>();
|
||||
List<Task> step1Tasks = new List<Task>();
|
||||
|
||||
// STEP 0
|
||||
step1Tasks.AddRange(
|
||||
[
|
||||
Task.Run(async () => _user = await AuthenticationService.GetUserAsync())
|
||||
]);
|
||||
endTasks.AddRange(
|
||||
[
|
||||
PhotosWebAPIService.GetPhotoRandomBackground(data => _defaultBackgroundPhoto = data)
|
||||
]);
|
||||
|
||||
// STEP 1
|
||||
await Task.WhenAll(step1Tasks);
|
||||
if (_user is not null)
|
||||
{
|
||||
endTasks.AddRange(
|
||||
[
|
||||
AccountsWebAPIService.GetAccountProfilePicture(_user.Id, data => _userProfilePicture = data)
|
||||
]);
|
||||
}
|
||||
|
||||
// END
|
||||
await Task.WhenAll(endTasks);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private PhotoResponse? GetBackgroundPhoto()
|
||||
{
|
||||
if (BackgroundPhoto?.Background is not null)
|
||||
{
|
||||
return BackgroundPhoto;
|
||||
}
|
||||
else if (_defaultBackgroundPhoto?.Background is not null)
|
||||
{
|
||||
return _defaultBackgroundPhoto;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Search
|
||||
|
||||
private void SearchStart()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_searchbarText))
|
||||
{
|
||||
string query = WebUtility.UrlEncode(_searchbarText);
|
||||
NavigationManager.NavigateTo($"/search/{query}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region User menu
|
||||
|
||||
private async Task UserMenuLogOut()
|
||||
{
|
||||
await AuthenticationService.LogoutAsync();
|
||||
await TokensService.RemoveAuthenticationData();
|
||||
NavigationManager.Refresh(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
body {
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 10px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.body-content {
|
||||
padding-top: 100px;
|
||||
}
|
||||
/* TAGS */
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* IDS */
|
||||
|
||||
#logo {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
#searchbarCancel {
|
||||
cursor: pointer;
|
||||
color: #6c757d;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PageTitle>WatchIt administrator panel</PageTitle>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="container-grid">
|
||||
@if (_loaded)
|
||||
{
|
||||
if (_authenticated)
|
||||
@@ -26,11 +26,21 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ErrorComponent ErrorMessage="You do not have permission to view this site"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="m-5">
|
||||
<LoadingComponent/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -1,11 +1,12 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Website.Layout;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
|
||||
namespace WatchIt.Website.Pages;
|
||||
|
||||
public partial class AdminPage
|
||||
{
|
||||
#region SERVICE
|
||||
#region SERVICES
|
||||
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
|
||||
@@ -13,6 +14,14 @@ public partial class AdminPage
|
||||
|
||||
|
||||
|
||||
#region PARAMETERS
|
||||
|
||||
[CascadingParameter] public MainLayout Layout { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded = false;
|
||||
@@ -28,6 +37,8 @@ public partial class AdminPage
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Layout.BackgroundPhoto = null;
|
||||
|
||||
User? user = await AuthenticationService.GetUserAsync();
|
||||
if (user is not null && user.IsAdmin)
|
||||
{
|
||||
|
||||
@@ -1,99 +1,98 @@
|
||||
@page "/auth"
|
||||
@layout EmptyLayout
|
||||
|
||||
<PageTitle>WatchIt - @(_authType == AuthType.SignIn ? "Sign in" : "Sign up")</PageTitle>
|
||||
|
||||
<PageTitle>WatchIt - @(_isSingUp ? "Sign up" : "Sign in")</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)
|
||||
<div class="panel panel-header rounded-3 p-3">
|
||||
<div class="d-flex flex-column align-items-center gap-3">
|
||||
<a id="logo" class="logo m-0" href="/">WatchIt</a>
|
||||
@if (_isSingUp)
|
||||
{
|
||||
<form method="post" @onsubmit="Login" @formname="login">
|
||||
<EditForm Model="@(_registerModel)">
|
||||
<AntiforgeryToken/>
|
||||
<div>
|
||||
<label>
|
||||
Username or email:
|
||||
<InputText @bind-Value="_loginModel!.UsernameOrEmail"/>
|
||||
</label>
|
||||
<div class="container-grid">
|
||||
<div class="row form-group mb-1">
|
||||
<label for="username" class="col-5 col-form-label">Username:</label>
|
||||
<div class="col">
|
||||
<InputText id="username" class="form-control" @bind-Value="_registerModel!.Username"/>
|
||||
</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 class="row form-group my-1">
|
||||
<label for="email" class="col-5 col-form-label">Email:</label>
|
||||
<div class="col">
|
||||
<InputText id="email" class="form-control" @bind-Value="_registerModel!.Email"/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="row form-group my-1">
|
||||
<label for="password" class="col-5 col-form-label">Password:</label>
|
||||
<div class="col">
|
||||
<InputText id="password" class="form-control" type="password" @bind-Value="_registerModel!.Password"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group my-1">
|
||||
<label for="confpassword" class="col-5 col-form-label">Confirm password:</label>
|
||||
<div class="col">
|
||||
<InputText id="confpassword" class="form-control" type="password" @bind-Value="_registerPasswordConfirmation"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col align-self-center">
|
||||
<span class="text-@(_formMessageIsSuccess ? "success" : "danger")">@_formMessage</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-dark" @onclick="@(Register)">Sign up</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" @onsubmit="Register" @formname="register">
|
||||
<EditForm Model="@(_loginModel)">
|
||||
<AntiforgeryToken/>
|
||||
<div>
|
||||
<label>
|
||||
Username:
|
||||
<InputText @bind-Value="_registerModel!.Username"/>
|
||||
</label>
|
||||
<div class="container-grid">
|
||||
<div class="row form-group mb-1">
|
||||
<label for="username" class="col-5 col-form-label">Username or email:</label>
|
||||
<div class="col">
|
||||
<InputText id="username" class="form-control" @bind-Value="_loginModel!.UsernameOrEmail"/>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Email:
|
||||
<InputText @bind-Value="_registerModel!.Email"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Password:
|
||||
<InputText type="password" @bind-Value="_registerModel!.Password"/>
|
||||
</label>
|
||||
<div class="row form-group my-1">
|
||||
<label for="password" class="col-5 col-form-label">Password:</label>
|
||||
<div class="col">
|
||||
<InputText id="password" type="password" class="form-control" @bind-Value="_loginModel!.Password"/>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Confirm password:
|
||||
<InputText type="password" @bind-Value="_passwordConfirmation"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Sign up</button>
|
||||
<div class="row form-group">
|
||||
<div class="col">
|
||||
<div class="form-check">
|
||||
<InputCheckbox class="form-check-input" @bind-Value="_loginModel!.RememberMe"/>
|
||||
<label class="form-check-label">Remember me</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col align-self-center">
|
||||
<span class="text-@(_formMessageIsSuccess ? "success" : "danger")">@_formMessage</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-dark" @onclick="@(Login)">Sign in</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
}
|
||||
<div class="btn-group w-100">
|
||||
<input type="radio" class="btn-check" name="signtype" id="signin" autocomplete="off" checked="@(!_isSingUp)" @onclick="() => { _isSingUp = false; _formMessage = null; _formMessageIsSuccess = false; }">
|
||||
<label class="btn btn-outline-secondary btn-sm" for="signin">Sign in</label>
|
||||
<input type="radio" class="btn-check" name="signtype" id="signup" autocomplete="off" checked="@(_isSingUp)" @onclick="() => { _isSingUp = true; _formMessage = null; _formMessageIsSuccess = false; }">
|
||||
<label class="btn btn-outline-secondary btn-sm" for="signup">Sign up</label>
|
||||
</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>
|
||||
@@ -101,17 +100,17 @@
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
height: 100%;
|
||||
/* TAGS */
|
||||
|
||||
background-image: url('@_background');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
body {
|
||||
background-image: url('@(_background is null ? "assets/background_temp.jpg": _background.ToString())');
|
||||
}
|
||||
|
||||
.logo {
|
||||
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
||||
|
||||
/* IDS */
|
||||
|
||||
#logo {
|
||||
background-image: linear-gradient(45deg, @(_background is null ? "#c6721c, #85200c" : $"#{Convert.ToHexString(_background.Background.FirstGradientColor)}, #{Convert.ToHexString(_background.Background.SecondGradientColor)}"));
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model.Accounts;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Photos;
|
||||
|
||||
namespace WatchIt.Website.Pages;
|
||||
|
||||
@@ -17,19 +20,17 @@ public partial class AuthPage
|
||||
[Inject] public ITokensService TokensService { get; set; } = default!;
|
||||
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
[Inject] public IAccountsWebAPIService AccountsWebAPIService { get; set; } = default!;
|
||||
[Inject] public IPhotosWebAPIService PhotosWebAPIService { get; set; } = default!;
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region ENUMS
|
||||
#region PARAMETERS
|
||||
|
||||
private enum AuthType
|
||||
{
|
||||
SignIn,
|
||||
SignUp
|
||||
}
|
||||
[SupplyParameterFromQuery(Name = "redirect_to")]
|
||||
private string RedirectTo { get; set; } = "/";
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -37,18 +38,13 @@ public partial class AuthPage
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded = false;
|
||||
private bool _loaded;
|
||||
|
||||
private AuthType _authType = AuthType.SignIn;
|
||||
private string _background = "assets/background_temp.jpg";
|
||||
private string _firstGradientColor = "#c6721c";
|
||||
private string _secondGradientColor = "#85200c";
|
||||
private PhotoResponse? _background;
|
||||
|
||||
private AuthenticateRequest _loginModel = new AuthenticateRequest
|
||||
{
|
||||
UsernameOrEmail = null,
|
||||
Password = null
|
||||
};
|
||||
private bool _isSingUp;
|
||||
private string? _formMessage;
|
||||
private bool _formMessageIsSuccess;
|
||||
|
||||
private RegisterRequest _registerModel = new RegisterRequest
|
||||
{
|
||||
@@ -56,9 +52,13 @@ public partial class AuthPage
|
||||
Email = null,
|
||||
Password = null
|
||||
};
|
||||
private string _passwordConfirmation;
|
||||
private string _registerPasswordConfirmation;
|
||||
|
||||
private IEnumerable<string> _errors;
|
||||
private AuthenticateRequest _loginModel = new AuthenticateRequest
|
||||
{
|
||||
UsernameOrEmail = null,
|
||||
Password = null
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -72,22 +72,19 @@ public partial class AuthPage
|
||||
{
|
||||
if (await AuthenticationService.GetAuthenticationStatusAsync())
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
NavigationManager.NavigateTo(WebUtility.UrlDecode(RedirectTo));
|
||||
}
|
||||
|
||||
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);
|
||||
List<Task> endTasks = new List<Task>();
|
||||
|
||||
_background = $"data:{data.MimeType};base64,{imageBase64}";
|
||||
_firstGradientColor = $"#{firstColor}";
|
||||
_secondGradientColor = $"#{secondColor}";
|
||||
};
|
||||
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess);
|
||||
// STEP 0
|
||||
endTasks.AddRange(
|
||||
[
|
||||
PhotosWebAPIService.GetPhotoRandomBackground(data => _background = data)
|
||||
]);
|
||||
|
||||
// END
|
||||
await Task.WhenAll(endTasks);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
@@ -96,44 +93,51 @@ public partial class AuthPage
|
||||
|
||||
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}");
|
||||
_formMessageIsSuccess = false;
|
||||
_formMessage = data.SelectMany(x => x.Value).FirstOrDefault();
|
||||
}
|
||||
|
||||
void LoginUnauthorized()
|
||||
{
|
||||
_errors = [ "Incorrect account data" ];
|
||||
_formMessageIsSuccess = false;
|
||||
_formMessage = "Incorrect account data";
|
||||
}
|
||||
|
||||
async Task LoginSuccess(AuthenticateResponse data)
|
||||
{
|
||||
await TokensService.SaveAuthenticationData(data);
|
||||
NavigationManager.NavigateTo(RedirectTo);
|
||||
}
|
||||
|
||||
|
||||
await AccountsWebAPIService.Authenticate(_loginModel, async (data) => await LoginSuccess(data), LoginBadRequest, LoginUnauthorized);
|
||||
}
|
||||
|
||||
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;
|
||||
_formMessageIsSuccess = true;
|
||||
_formMessage = "You are registered. You can sign in now.";
|
||||
_isSingUp = false;
|
||||
}
|
||||
|
||||
void RegisterBadRequest(IDictionary<string, string[]> data)
|
||||
{
|
||||
_errors = data.SelectMany(x => x.Value).Select(x => $"• {x}");
|
||||
_formMessageIsSuccess = false;
|
||||
_formMessage = data.SelectMany(x => x.Value).FirstOrDefault();
|
||||
}
|
||||
|
||||
|
||||
if (_registerModel.Password != _registerPasswordConfirmation)
|
||||
{
|
||||
_formMessageIsSuccess = false;
|
||||
_formMessage = "Password fields don't match";
|
||||
return;
|
||||
}
|
||||
await AccountsWebAPIService.Register(_registerModel, RegisterSuccess, RegisterBadRequest);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -4,6 +4,14 @@ html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* CLASSES */
|
||||
|
||||
63
WatchIt.Website/WatchIt.Website/Pages/DatabasePage.razor
Normal file
63
WatchIt.Website/WatchIt.Website/Pages/DatabasePage.razor
Normal file
@@ -0,0 +1,63 @@
|
||||
@using WatchIt.Common.Model.Movies
|
||||
@using WatchIt.Common.Model.Series
|
||||
@using WatchIt.Website.Components.DatabasePage
|
||||
|
||||
@page "/database/{type?}"
|
||||
|
||||
|
||||
|
||||
@if (_loaded)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case "movies":
|
||||
<DatabasePageComponent TItem="MovieResponse"
|
||||
TQuery="MovieQueryParameters"
|
||||
Title="Movies database"
|
||||
IdSource="@(item => item.Id)"
|
||||
NameSource="@(item => item.Title)"
|
||||
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
|
||||
RatingSource="@(item => item.Rating)"
|
||||
UrlIdTemplate="/media/{0}"
|
||||
PictureDownloadingTask="@((id, action) => MediaWebAPIService.GetMediaPoster(id, action))"
|
||||
ItemDownloadingTask="@(MoviesWebAPIService.GetAllMovies)"
|
||||
SortingOptions="@(new Dictionary<string, string>
|
||||
{
|
||||
{ "rating.count", "Number of ratings" },
|
||||
{ "rating.average", "Average rating" },
|
||||
{ "title", "Title" },
|
||||
{ "release_date", "Release date" },
|
||||
})">
|
||||
<MoviesFilterFormComponent/>
|
||||
</DatabasePageComponent>
|
||||
break;
|
||||
case "series":
|
||||
<DatabasePageComponent TItem="SeriesResponse"
|
||||
TQuery="SeriesQueryParameters"
|
||||
Title="TV series database"
|
||||
IdSource="@(item => item.Id)"
|
||||
NameSource="@(item => item.Title)"
|
||||
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
|
||||
RatingSource="@(item => item.Rating)"
|
||||
UrlIdTemplate="/media/{0}"
|
||||
PictureDownloadingTask="@((id, action) => MediaWebAPIService.GetMediaPoster(id, action))"
|
||||
ItemDownloadingTask="@(SeriesWebAPIService.GetAllSeries)"
|
||||
SortingOptions="@(new Dictionary<string, string>
|
||||
{
|
||||
{ "rating.count", "Number of ratings" },
|
||||
{ "rating.average", "Average rating" },
|
||||
{ "title", "Title" },
|
||||
{ "release_date", "Release date" },
|
||||
})">
|
||||
<SeriesFilterFormComponent/>
|
||||
</DatabasePageComponent>
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="m-5">
|
||||
<LoadingComponent/>
|
||||
</div>
|
||||
}
|
||||
58
WatchIt.Website/WatchIt.Website/Pages/DatabasePage.razor.cs
Normal file
58
WatchIt.Website/WatchIt.Website/Pages/DatabasePage.razor.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Website.Components.DatabasePage;
|
||||
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 DatabasePage : ComponentBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] private IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
[Inject] private IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!;
|
||||
[Inject] private ISeriesWebAPIService SeriesWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter] public string? Type { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private static IEnumerable<string> _databaseTypes = ["movies", "series"];
|
||||
|
||||
private bool _loaded;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// INIT
|
||||
if (!_databaseTypes.Contains(Type))
|
||||
{
|
||||
NavigationManager.NavigateTo($"/database/{_databaseTypes.First()}");
|
||||
}
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
@page "/"
|
||||
@using WatchIt.Common.Model.Movies
|
||||
|
||||
<PageTitle>WatchIt</PageTitle>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="container-grid">
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error))
|
||||
@@ -11,7 +10,7 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-4">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="container-grid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4><strong>Top 5 movies this week by popularity</strong></h4>
|
||||
@@ -26,7 +25,7 @@
|
||||
<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="container-grid">
|
||||
<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>
|
||||
@@ -49,7 +48,7 @@
|
||||
<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="container-grid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4><strong>Top 5 TV series this week by popularity</strong></h4>
|
||||
@@ -64,7 +63,7 @@
|
||||
<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="container-grid">
|
||||
<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>
|
||||
@@ -87,11 +86,21 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ErrorComponent ErrorMessage="@_error"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="m-5">
|
||||
<LoadingComponent/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -2,6 +2,7 @@
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Website.Layout;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Movies;
|
||||
using WatchIt.Website.Services.WebAPI.Series;
|
||||
@@ -21,6 +22,14 @@ public partial class HomePage
|
||||
|
||||
|
||||
|
||||
#region PARAMETERS
|
||||
|
||||
[CascadingParameter] public MainLayout Layout { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded;
|
||||
@@ -39,6 +48,8 @@ public partial class HomePage
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Layout.BackgroundPhoto = null;
|
||||
|
||||
List<Task> step1Tasks = new List<Task>();
|
||||
List<Task> endTasks = new List<Task>();
|
||||
|
||||
@@ -53,8 +64,8 @@ public partial class HomePage
|
||||
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))
|
||||
Parallel.ForEachAsync(_topMovies, async (x, _) => await MediaWebAPIService.GetMediaPoster(x.Key.Id, y => _topMovies[x.Key] = y)),
|
||||
Parallel.ForEachAsync(_topSeries, async (x, _) => await MediaWebAPIService.GetMediaPoster(x.Key.Id, y => _topSeries[x.Key] = y))
|
||||
]);
|
||||
|
||||
// END
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@using WatchIt.Common.Model.Movies
|
||||
@using Microsoft.IdentityModel.Tokens
|
||||
@using WatchIt.Common.Model.Movies
|
||||
@using WatchIt.Common.Model.Photos
|
||||
@using WatchIt.Common.Model.Series
|
||||
|
||||
@page "/media/{id:long}/edit"
|
||||
@page "/media/new/{type}"
|
||||
@page "/media/new/{type?}"
|
||||
|
||||
|
||||
<PageTitle>
|
||||
@@ -38,7 +40,7 @@
|
||||
}
|
||||
</PageTitle>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="container-grid">
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error))
|
||||
@@ -48,14 +50,14 @@
|
||||
<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>
|
||||
<h3 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)</h3>
|
||||
</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="container-grid">
|
||||
<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"/>
|
||||
@@ -75,12 +77,12 @@
|
||||
<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>
|
||||
<span>Save poster</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Saving...</span>
|
||||
<span>Saving...</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
@@ -94,12 +96,12 @@
|
||||
<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>
|
||||
<span>Delete poster</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Deleting...</span>
|
||||
<span>Deleting...</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
@@ -113,7 +115,7 @@
|
||||
<div class="rounded-3 panel panel-regular p-4 h-100">
|
||||
<EditForm Model="_mediaRequest">
|
||||
<AntiforgeryToken/>
|
||||
<div class="container-fluid p-0">
|
||||
<div class="container-grid">
|
||||
<div class="row form-group mb-1">
|
||||
<label for="title" class="col-2 col-form-label">Title*</label>
|
||||
<div class="col-10">
|
||||
@@ -181,12 +183,12 @@
|
||||
<button type="button" class="btn btn-secondary" @onclick="SaveBasicData">
|
||||
@if (!_basicDataSaving)
|
||||
{
|
||||
<span class="sr-only">Save</span>
|
||||
<span>Save</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span class="sr-only">Saving...</span>
|
||||
<span>Saving...</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
@@ -197,34 +199,193 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-4">
|
||||
<div class="container-grid">
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-center h-100">
|
||||
<h4 class="m-0"><strong>Photos</strong></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
@if (!_photoEditMode)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" disabled="@(!Id.HasValue)" @onclick="() => InitEditPhoto(null)">Add new photo</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ErrorComponent ErrorMessage="You do not have permission to view this site"/>
|
||||
<div class="d-flex gap-3 align-items-center">
|
||||
@if (!string.IsNullOrWhiteSpace(_photoEditError))
|
||||
{
|
||||
<div class="text-danger">
|
||||
@_photoEditError
|
||||
</div>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" disabled="@(_photoEditSaving)" @onclick="SaveEditPhoto">
|
||||
@if (!_photoEditSaving)
|
||||
{
|
||||
<span>Save</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ErrorComponent ErrorMessage="@_error"/>
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
<span>Saving...</span>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<LoadingComponent/>
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" disabled="@(_photoEditSaving)" @onclick="CancelEditPhoto">Cancel</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@if (_background is not null)
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@if (!_photoEditMode)
|
||||
{
|
||||
<style>
|
||||
body {
|
||||
background-image: url('@($"data:{_background.MimeType};base64,{Convert.ToBase64String(_background.Image)}")') !important;
|
||||
if (!_photos.IsNullOrEmpty())
|
||||
{
|
||||
<div id="scrollPhotos" class="d-flex p-3 gap-3" data-bs-spy="scroll" tabindex="0">
|
||||
@foreach (PhotoResponse photo in _photos)
|
||||
{
|
||||
<div class="container-grid photo-container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img class="rounded-1 shadow object-fit-cover photo-default-aspect-ratio" src="@(photo.ToString())" alt="photo" width="350"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2 gx-2">
|
||||
@if (photo.Background is not null)
|
||||
{
|
||||
<div class="col-auto">
|
||||
<div class="d-flex align-items-center">
|
||||
<div id="backgroundIndicator" class="border rounded-circle circle-@(photo.Background.IsUniversalBackground ? "blue" : "grey") p-1" data-toggle="tooltip" data-placement="top" title="@(photo.Background.IsUniversalBackground ? "Universal" : "Media-only") background">
|
||||
<img class="no-vertical-align" src="assets/icons/background.png" alt="background_icon" height="20px" width="20px"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
.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;
|
||||
<div class="col">
|
||||
<div class="d-flex align-items-center h-100 text-size-upload-date">
|
||||
Upload: @(photo.UploadDate.ToString())
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-secondary btn-sm" @onclick="() => InitEditPhoto(photo.Id)" disabled="@(_photoDeleting.Contains(photo.Id))">
|
||||
<img src="assets/icons/edit.png" alt="edit_icon" height="20px" width="20px"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-danger btn-sm" disabled="@(_photoDeleting.Contains(photo.Id))" @onclick="() => DeletePhoto(photo.Id)">
|
||||
@if (!_photoDeleting.Contains(photo.Id))
|
||||
{
|
||||
<img src="assets/icons/delete.png" alt="delete_icon" height="20px" width="20px"/>
|
||||
}
|
||||
</style>
|
||||
else
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex justify-content-center">
|
||||
Photo list is empty
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="container-grid">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<div class="container-grid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img class="rounded-1 shadow object-fit-cover photo-default-aspect-ratio" src="@(_photoEditRequest is null ? "assets/photo.png" : _photoEditRequest.ToString())" alt="edit_photo" width="300px"/>
|
||||
</div>
|
||||
</div>
|
||||
@if (_photoEditId is null)
|
||||
{
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
<InputFile class="form-control" OnChange="LoadPhoto" autocomplete="off" style="width: 300px;"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="container-grid">
|
||||
<div class="row form-group">
|
||||
<div class="col">
|
||||
<div class="form-check">
|
||||
<InputCheckbox class="form-check-input" @bind-Value="_photoEditIsBackground"/>
|
||||
<label class="form-check-label">Use as background</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-check">
|
||||
<InputCheckbox class="form-check-input" @bind-Value="_photoEditBackgroundData.IsUniversalBackground" disabled="@(!_photoEditIsBackground)"/>
|
||||
<label class="form-check-label">Use as universal background</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group my-1">
|
||||
<label for="first-gradient-color" class="col-4 col-form-label">First gradient color</label>
|
||||
<div class="col-8">
|
||||
<input type="color" class="form-control form-control-color w-100" id="first-gradient-color" value="#@(Convert.ToHexString(_photoEditBackgroundData.FirstGradientColor))" disabled="@(!_photoEditIsBackground)" @onchange="EditPhotoFirstGradientColorChanged">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label for="second-gradient-color" class="col-4 col-form-label">Second gradient color</label>
|
||||
<div class="col-8">
|
||||
<input type="color" class="form-control form-control-color w-100" id="second-gradient-color" value="#@(Convert.ToHexString(_photoEditBackgroundData.SecondGradientColor))" disabled="@(!_photoEditIsBackground)" @onchange="EditPhotoSecondGradientColorChanged">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ErrorComponent ErrorMessage="You do not have permission to view this site"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ErrorComponent ErrorMessage="@_error"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="m-5">
|
||||
<LoadingComponent/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -3,10 +3,13 @@ using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Website.Layout;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Movies;
|
||||
using WatchIt.Website.Services.WebAPI.Photos;
|
||||
using WatchIt.Website.Services.WebAPI.Series;
|
||||
|
||||
namespace WatchIt.Website.Pages;
|
||||
@@ -20,6 +23,7 @@ public partial class MediaEditPage : ComponentBase
|
||||
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
[Inject] public IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!;
|
||||
[Inject] public ISeriesWebAPIService SeriesWebAPIService { get; set; } = default!;
|
||||
[Inject] public IPhotosWebAPIService PhotosWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -30,6 +34,8 @@ public partial class MediaEditPage : ComponentBase
|
||||
[Parameter] public long? Id { get; set; }
|
||||
[Parameter] public string? Type { get; set; }
|
||||
|
||||
[CascadingParameter] public MainLayout Layout { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -41,8 +47,6 @@ public partial class MediaEditPage : ComponentBase
|
||||
|
||||
private User? _user;
|
||||
|
||||
private MediaPhotoResponse? _background;
|
||||
|
||||
private MediaResponse? _media;
|
||||
private MovieRequest? _movieRequest;
|
||||
private SeriesRequest? _seriesRequest;
|
||||
@@ -56,6 +60,21 @@ public partial class MediaEditPage : ComponentBase
|
||||
private bool _mediaPosterSaving;
|
||||
private bool _mediaPosterDeleting;
|
||||
|
||||
private IEnumerable<PhotoResponse> _photos = new List<PhotoResponse>();
|
||||
private List<Guid> _photoDeleting = new List<Guid>();
|
||||
private bool _photoEditMode;
|
||||
private string? _photoEditError;
|
||||
private Guid? _photoEditId;
|
||||
private bool _photoEditSaving;
|
||||
private bool _photoEditIsBackground;
|
||||
private MediaPhotoRequest? _photoEditRequest;
|
||||
private PhotoBackgroundDataRequest? _photoEditBackgroundData = new PhotoBackgroundDataRequest()
|
||||
{
|
||||
FirstGradientColor = [0xFF, 0xFF, 0xFF],
|
||||
SecondGradientColor = [0x00, 0x00, 0x00],
|
||||
IsUniversalBackground = false
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -68,6 +87,8 @@ public partial class MediaEditPage : ComponentBase
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Layout.BackgroundPhoto = null;
|
||||
|
||||
List<Task> step1Tasks = new List<Task>();
|
||||
List<Task> step2Tasks = new List<Task>();
|
||||
List<Task> endTasks = new List<Task>();
|
||||
@@ -94,12 +115,13 @@ public partial class MediaEditPage : ComponentBase
|
||||
{
|
||||
endTasks.AddRange(
|
||||
[
|
||||
MediaWebAPIService.GetPhotoMediaRandomBackground(Id.Value, data => _background = data),
|
||||
MediaWebAPIService.GetPoster(Id.Value, data =>
|
||||
MediaWebAPIService.GetMediaPhotoRandomBackground(Id.Value, data => Layout.BackgroundPhoto = data),
|
||||
MediaWebAPIService.GetMediaPoster(Id.Value, data =>
|
||||
{
|
||||
_mediaPosterSaved = data;
|
||||
_mediaPosterRequest = new MediaPosterRequest(data);
|
||||
})
|
||||
}),
|
||||
MediaWebAPIService.GetMediaPhotos(Id.Value, successAction: data => _photos = data)
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -179,7 +201,7 @@ public partial class MediaEditPage : ComponentBase
|
||||
}
|
||||
|
||||
_mediaPosterSaving = true;
|
||||
await MediaWebAPIService.PutPoster(Id.Value, _mediaPosterRequest, Success);
|
||||
await MediaWebAPIService.PutMediaPoster(Id.Value, _mediaPosterRequest, Success);
|
||||
}
|
||||
|
||||
private void CancelPoster()
|
||||
@@ -199,7 +221,7 @@ public partial class MediaEditPage : ComponentBase
|
||||
}
|
||||
|
||||
_mediaPosterDeleting = true;
|
||||
await MediaWebAPIService.DeletePoster(Id.Value, Success);
|
||||
await MediaWebAPIService.DeleteMediaPoster(Id.Value, Success);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -250,5 +272,116 @@ public partial class MediaEditPage : ComponentBase
|
||||
|
||||
#endregion
|
||||
|
||||
#region Photos
|
||||
|
||||
private async Task DeletePhoto(Guid id)
|
||||
{
|
||||
async Task Success()
|
||||
{
|
||||
NavigationManager.Refresh(true);
|
||||
}
|
||||
|
||||
_photoDeleting.Add(id);
|
||||
await PhotosWebAPIService.DeletePhoto(id, async () => await Success());
|
||||
}
|
||||
|
||||
private void InitEditPhoto(Guid? id)
|
||||
{
|
||||
_photoEditMode = true;
|
||||
_photoEditId = id;
|
||||
_photoEditRequest = null;
|
||||
_photoEditIsBackground = false;
|
||||
_photoEditBackgroundData = new PhotoBackgroundDataRequest
|
||||
{
|
||||
FirstGradientColor = [0xFF, 0xFF, 0xFF],
|
||||
SecondGradientColor = [0x00, 0x00, 0x00],
|
||||
IsUniversalBackground = false
|
||||
};
|
||||
if (id is not null)
|
||||
{
|
||||
PhotoResponse response = _photos.First(x => x.Id == id);
|
||||
_photoEditRequest = new MediaPhotoRequest(response);
|
||||
if (response.Background is not null)
|
||||
{
|
||||
_photoEditIsBackground = true;
|
||||
_photoEditBackgroundData = new PhotoBackgroundDataRequest(response.Background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelEditPhoto()
|
||||
{
|
||||
_photoEditMode = false;
|
||||
_photoEditId = null;
|
||||
_photoEditError = null;
|
||||
}
|
||||
|
||||
private async Task SaveEditPhoto()
|
||||
{
|
||||
void Success()
|
||||
{
|
||||
NavigationManager.Refresh(true);
|
||||
}
|
||||
|
||||
void BadRequest(IDictionary<string, string[]> errors)
|
||||
{
|
||||
_photoEditError = errors.SelectMany(x => x.Value).FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(_basicDataError))
|
||||
{
|
||||
_photoEditSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
_photoEditSaving = true;
|
||||
if (_photoEditId is null)
|
||||
{
|
||||
_photoEditRequest.Background = _photoEditIsBackground ? _photoEditBackgroundData : null;
|
||||
await MediaWebAPIService.PostMediaPhoto(Id.Value, _photoEditRequest, Success, BadRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_photoEditIsBackground)
|
||||
{
|
||||
await PhotosWebAPIService.PutPhotoBackgroundData(_photoEditId.Value, _photoEditBackgroundData, Success, BadRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
await PhotosWebAPIService.DeletePhotoBackgroundData(_photoEditId.Value, Success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadPhoto(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();
|
||||
}
|
||||
|
||||
_photoEditRequest = new MediaPhotoRequest
|
||||
{
|
||||
Image = array,
|
||||
MimeType = args.File.ContentType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void EditPhotoFirstGradientColorChanged(ChangeEventArgs e)
|
||||
{
|
||||
_photoEditBackgroundData.FirstGradientColor = Convert.FromHexString(e.Value.ToString().Replace("#", string.Empty));
|
||||
}
|
||||
|
||||
private void EditPhotoSecondGradientColorChanged(ChangeEventArgs e)
|
||||
{
|
||||
_photoEditBackgroundData.SecondGradientColor = Convert.FromHexString(e.Value.ToString().Replace("#", string.Empty));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* IDS */
|
||||
|
||||
#scrollPhotos {
|
||||
overflow-x: scroll;
|
||||
|
||||
border-color: grey;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#backgroundIndicator {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* CLASSES */
|
||||
|
||||
.circle-blue {
|
||||
background-color: dodgerblue;
|
||||
border-color: dodgerblue;
|
||||
}
|
||||
|
||||
.circle-grey {
|
||||
background-color: grey;
|
||||
border-color: grey;
|
||||
}
|
||||
|
||||
.no-vertical-align {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
.photo-container {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.text-size-upload-date {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.photo-default-aspect-ratio {
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ else
|
||||
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="container-grid">
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error))
|
||||
@@ -35,7 +35,7 @@ else
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="d-flex h-100">
|
||||
<div class="container-fluid px-0 align-self-end">
|
||||
<div class="container-grid align-self-end">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="align-self-end title-shadow">
|
||||
@@ -60,7 +60,7 @@ else
|
||||
<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="container-grid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
@@ -133,11 +133,11 @@ else
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="rounded-3 panel panel-yellow p-4 h-100">
|
||||
<div class="container-fluid px-0">
|
||||
<div class="container-grid">
|
||||
<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")
|
||||
<strong>Global rating:</strong> @(_globalRating.Count == 0 ? "no ratings" : $"{Math.Round(_globalRating.Average, 1)}/10")
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
@@ -193,26 +193,21 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ErrorComponent ErrorMessage="@_error"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="m-5">
|
||||
<LoadingComponent/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</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>
|
||||
}
|
||||
@@ -3,7 +3,10 @@ using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model.Genres;
|
||||
using WatchIt.Common.Model.Media;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Common.Model.Photos;
|
||||
using WatchIt.Common.Model.Rating;
|
||||
using WatchIt.Common.Model.Series;
|
||||
using WatchIt.Website.Layout;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Movies;
|
||||
@@ -29,6 +32,8 @@ public partial class MediaPage : ComponentBase
|
||||
|
||||
[Parameter] public long Id { get; set; }
|
||||
|
||||
[CascadingParameter] public MainLayout Layout { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -42,10 +47,9 @@ public partial class MediaPage : ComponentBase
|
||||
|
||||
private User? _user;
|
||||
|
||||
private MediaPhotoResponse? _background;
|
||||
private MediaPosterResponse? _poster;
|
||||
private IEnumerable<GenreResponse> _genres;
|
||||
private MediaRatingResponse _globalRating;
|
||||
private RatingResponse _globalRating;
|
||||
private MovieResponse? _movie;
|
||||
private SeriesResponse? _series;
|
||||
|
||||
@@ -61,6 +65,8 @@ public partial class MediaPage : ComponentBase
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Layout.BackgroundPhoto = null;
|
||||
|
||||
List<Task> step1Tasks = new List<Task>();
|
||||
List<Task> step2Tasks = new List<Task>();
|
||||
List<Task> endTasks = new List<Task>();
|
||||
@@ -83,8 +89,8 @@ public partial class MediaPage : ComponentBase
|
||||
endTasks.AddRange(
|
||||
[
|
||||
MediaWebAPIService.PostMediaView(Id),
|
||||
MediaWebAPIService.GetPhotoMediaRandomBackground(Id, data => _background = data),
|
||||
MediaWebAPIService.GetPoster(Id, data => _poster = data),
|
||||
MediaWebAPIService.GetMediaPhotoRandomBackground(Id, data => Layout.BackgroundPhoto = data),
|
||||
MediaWebAPIService.GetMediaPoster(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),
|
||||
@@ -118,7 +124,7 @@ public partial class MediaPage : ComponentBase
|
||||
}
|
||||
else
|
||||
{
|
||||
await MediaWebAPIService.PutMediaRating(Id, new MediaRatingRequest(rating));
|
||||
await MediaWebAPIService.PutMediaRating(Id, new RatingRequest(rating));
|
||||
_userRating = rating;
|
||||
}
|
||||
await MediaWebAPIService.GetMediaRating(Id, data => _globalRating = data);
|
||||
|
||||
79
WatchIt.Website/WatchIt.Website/Pages/SearchPage.razor
Normal file
79
WatchIt.Website/WatchIt.Website/Pages/SearchPage.razor
Normal file
@@ -0,0 +1,79 @@
|
||||
@using WatchIt.Common.Model.Movies
|
||||
@using WatchIt.Common.Model.Series
|
||||
@using WatchIt.Common.Query
|
||||
@using WatchIt.Website.Components.SearchPage
|
||||
@using WatchIt.Website.Services.WebAPI.Movies
|
||||
|
||||
@layout MainLayout
|
||||
|
||||
@page "/search/{query}"
|
||||
|
||||
<PageTitle>WatchIt - Searching "@(Query)"</PageTitle>
|
||||
|
||||
|
||||
|
||||
<div class="container-grid">
|
||||
@if (_loaded)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_error))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="rounded-3 panel panel-regular p-3">
|
||||
<div class="d-flex justify-content-center">
|
||||
<h3 class="m-0">
|
||||
<strong>Search results for phrase:</strong> "@(DecodedQuery)"
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
<SearchResultComponent TItem="MovieResponse"
|
||||
TQuery="MovieQueryParameters"
|
||||
Title="Movies"
|
||||
UrlIdTemplate="/media/{0}"
|
||||
IdSource="@(item => item.Id)"
|
||||
NameSource="@(item => item.Title)"
|
||||
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
|
||||
RatingSource="@(item => item.Rating)"
|
||||
Query="@(new MovieQueryParameters { Title = DecodedQuery, OrderBy = "rating.count" })"
|
||||
ItemDownloadingTask="@(MoviesWebAPIService.GetAllMovies)"
|
||||
PictureDownloadingTask="@((id, action) => MediaWebAPIService.GetMediaPoster(id, action))"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
<SearchResultComponent TItem="SeriesResponse"
|
||||
TQuery="SeriesQueryParameters"
|
||||
Title="TV series"
|
||||
UrlIdTemplate="/media/{0}"
|
||||
IdSource="@(item => item.Id)"
|
||||
NameSource="@(item => item.Title)"
|
||||
AdditionalNameInfoSource="@(item => item.ReleaseDate.HasValue ? $" ({item.ReleaseDate.Value.Year})" : null)"
|
||||
RatingSource="@(item => item.Rating)"
|
||||
Query="@(new SeriesQueryParameters { Title = DecodedQuery, OrderBy = "rating.count" })"
|
||||
ItemDownloadingTask="@(SeriesWebAPIService.GetAllSeries)"
|
||||
PictureDownloadingTask="@((id, action) => MediaWebAPIService.GetMediaPoster(id, action))"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ErrorComponent ErrorMessage="@(_error)"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<LoadingComponent/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
61
WatchIt.Website/WatchIt.Website/Pages/SearchPage.razor.cs
Normal file
61
WatchIt.Website/WatchIt.Website/Pages/SearchPage.razor.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Website.Layout;
|
||||
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 SearchPage : ComponentBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
[Inject] private IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!;
|
||||
[Inject] private ISeriesWebAPIService SeriesWebAPIService { get; set; } = default!;
|
||||
[Inject] private IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded;
|
||||
private string? _error;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter] public string Query { get; set; }
|
||||
|
||||
[CascadingParameter] public MainLayout Layout { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public string DecodedQuery => WebUtility.UrlDecode(Query);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Blazorise;
|
||||
using Blazorise.Bootstrap5;
|
||||
using Blazorise.Icons.FontAwesome;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
@@ -8,6 +11,7 @@ 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.Photos;
|
||||
using WatchIt.Website.Services.WebAPI.Series;
|
||||
|
||||
namespace WatchIt.Website;
|
||||
@@ -52,6 +56,12 @@ public static class Program
|
||||
private static WebApplicationBuilder SetupServices(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddSingleton<HttpClient>();
|
||||
builder.Services.AddBlazorise(options =>
|
||||
{
|
||||
options.Immediate = true;
|
||||
})
|
||||
.AddBootstrap5Providers()
|
||||
.AddFontAwesomeIcons();
|
||||
|
||||
// Utility
|
||||
builder.Services.AddSingleton<IHttpClientService, HttpClientService>();
|
||||
@@ -64,6 +74,7 @@ public static class Program
|
||||
builder.Services.AddSingleton<IMediaWebAPIService, MediaWebAPIService>();
|
||||
builder.Services.AddSingleton<IMoviesWebAPIService, MoviesWebAPIService>();
|
||||
builder.Services.AddSingleton<ISeriesWebAPIService, SeriesWebAPIService>();
|
||||
builder.Services.AddSingleton<IPhotosWebAPIService, PhotosWebAPIService>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<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.Photos\WatchIt.Website.Services.WebAPI.Photos.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Series\WatchIt.Website.Services.WebAPI.Series.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -34,10 +35,16 @@
|
||||
<_ContentIncludedByDefault Remove="Components\Pages\Weather.razor" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css.map" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\scripts\popper.min.js" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Layout\MainLayout.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazorise.Bootstrap5" Version="1.6.1" />
|
||||
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="1.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
@using WatchIt.Website.Services.Utility.Authentication
|
||||
@using WatchIt.Website.Services.WebAPI.Accounts
|
||||
@using WatchIt.Website.Services.WebAPI.Media
|
||||
@using Blazorise
|
||||
@using Blazorise.Bootstrap5
|
||||
@@ -30,26 +30,21 @@
|
||||
},
|
||||
"Media": {
|
||||
"Base": "/media",
|
||||
"Get": "/{0}",
|
||||
"GetGenres": "/{0}/genres",
|
||||
"PostGenre": "/{0}/genres/{1}",
|
||||
"DeleteGenre": "/{0}/genres/{1}",
|
||||
"GetMedia": "/{0}",
|
||||
"GetMediaGenres": "/{0}/genres",
|
||||
"PostMediaGenre": "/{0}/genres/{1}",
|
||||
"DeleteMediaGenre": "/{0}/genres/{1}",
|
||||
"GetMediaRating": "/{0}/rating",
|
||||
"GetMediaRatingByUser": "/{0}/rating/{1}",
|
||||
"PutMediaRating": "/{0}/rating",
|
||||
"DeleteMediaRating": "/{0}/rating",
|
||||
"PostMediaView": "/{0}/view",
|
||||
|
||||
"GetPhotoMediaRandomBackground": "/{0}/photos/random_background",
|
||||
"GetPoster": "/{0}/poster",
|
||||
"PutPoster": "/{0}/poster",
|
||||
"DeletePoster": "/{0}/poster",
|
||||
"GetPhoto": "/photos/{0}",
|
||||
"GetPhotos": "/photos",
|
||||
"GetPhotoRandomBackground": "/photos/random_background",
|
||||
"PostPhoto": "/photos",
|
||||
"PutPhoto": "/photos/{0}",
|
||||
"DeletePhoto": "/photos/{0}"
|
||||
"GetMediaPoster": "/{0}/poster",
|
||||
"PutMediaPoster": "/{0}/poster",
|
||||
"DeleteMediaPoster": "/{0}/poster",
|
||||
"GetMediaPhotos": "/{0}/photos",
|
||||
"GetMediaPhotoRandomBackground": "/{0}/photos/random_background",
|
||||
"PostMediaPhoto": "/{0}/photos"
|
||||
},
|
||||
"Movies": {
|
||||
"Base": "/movies",
|
||||
@@ -68,6 +63,13 @@
|
||||
"PutSeries": "/{0}",
|
||||
"DeleteSeries": "/{0}",
|
||||
"GetSeriesViewRank": "/view"
|
||||
},
|
||||
"Photos": {
|
||||
"Base": "/photos",
|
||||
"GetPhotoRandomBackground": "/random_background",
|
||||
"DeletePhoto": "/{0}",
|
||||
"PutPhotoBackgroundData": "/{0}/background_data",
|
||||
"DeletePhotoBackgroundData": "/{0}/background_data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 887 B |
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/icons/cancel.png
Normal file
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/icons/cancel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 581 B |
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/icons/delete.png
Normal file
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/icons/delete.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/icons/edit.png
Normal file
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/icons/edit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 634 B |
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/photo.png
Normal file
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/photo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
99
WatchIt.Website/WatchIt.Website/wwwroot/css/general.css
Normal file
99
WatchIt.Website/WatchIt.Website/wwwroot/css/general.css
Normal file
@@ -0,0 +1,99 @@
|
||||
/* TAGS */
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body, html {
|
||||
background-color: transparent;
|
||||
|
||||
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
color: lightgray;
|
||||
font-family: "PT Sans";
|
||||
}
|
||||
|
||||
/* CLASSES */
|
||||
|
||||
.container-grid {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
--bs-gutter-x: 1.5rem;
|
||||
--bs-gutter-y: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: "Belanosima";
|
||||
text-decoration: none;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.top-3 {
|
||||
top: 1rem !important;
|
||||
}
|
||||
|
||||
.mb-2rem {
|
||||
margin-bottom: 2rem !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.mt-9 {
|
||||
margin-top: 9rem !important;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.panel-regular {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.panel-yellow {
|
||||
background-color: rgba(255, 184, 58, 0.6);
|
||||
}
|
||||
|
||||
.panel {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(25px);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.dropdown-menu-left {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.btn-stretch-x {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.picture-aspect-ratio {
|
||||
aspect-ratio: 3/5;
|
||||
}
|
||||
@@ -1,40 +1,4 @@
|
||||
body, html {
|
||||
background-color: transparent;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
color: lightgray;
|
||||
font-family: "PT Sans";
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: "Belanosima";
|
||||
text-decoration: none;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.mt-9 {
|
||||
margin-top: 9rem !important;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.panel-regular {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.panel-yellow {
|
||||
background-color: rgba(255, 184, 58, 0.6);
|
||||
}
|
||||
|
||||
.panel {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(25px);
|
||||
}
|
||||
/* CLASSES */
|
||||
|
||||
.main-button {
|
||||
--r:10px;
|
||||
@@ -84,12 +48,3 @@ body, html {
|
||||
.main-button:hover::before {
|
||||
-webkit-mask:none;
|
||||
}
|
||||
|
||||
.dropdown-menu-left {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.btn-stretch-x {
|
||||
width: 100%;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user