authentication refresh fixed, movie creation page added
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Net.Http.Headers;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||
|
||||
namespace WatchIt.Website.Services.Utility.Authentication;
|
||||
|
||||
public class AuthenticationService : IAuthenticationService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly AuthenticationStateProvider _authenticationStateProvider;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ITokensService _tokensService;
|
||||
private readonly IAccountsWebAPIService _accountsWebAPIService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public AuthenticationService(AuthenticationStateProvider authenticationStateProvider, HttpClient httpClient, ITokensService tokensService, IAccountsWebAPIService accountsWebAPIService)
|
||||
{
|
||||
_authenticationStateProvider = authenticationStateProvider;
|
||||
_httpClient = httpClient;
|
||||
_tokensService = tokensService;
|
||||
_accountsWebAPIService = accountsWebAPIService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<User?> GetUserAsync()
|
||||
{
|
||||
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||
|
||||
if (!GetAuthenticationStatusAsync(state))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new User
|
||||
{
|
||||
Id = int.Parse(state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.Sub)!.Value),
|
||||
Username = state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.UniqueName)!.Value,
|
||||
Email = state.User.FindFirst(x => x.Type == JwtRegisteredClaimNames.Email)!.Value,
|
||||
IsAdmin = bool.Parse(state.User.FindFirst(x => x.Type == "admin")!.Value),
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> GetAuthenticationStatusAsync()
|
||||
{
|
||||
AuthenticationState state = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
||||
return GetAuthenticationStatusAsync(state);
|
||||
}
|
||||
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
string? refreshToken = await _tokensService.GetRefreshToken();
|
||||
if (refreshToken is not null)
|
||||
{
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("refresh", refreshToken.Replace("\"", ""));
|
||||
await _accountsWebAPIService.Logout();
|
||||
_httpClient.DefaultRequestHeaders.Authorization = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private bool GetAuthenticationStatusAsync(AuthenticationState state)
|
||||
{
|
||||
return state.User.HasClaim(x => x.Type == JwtRegisteredClaimNames.Iss && x.Value == "WatchIt");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace WatchIt.Website.Services.Utility.Authentication;
|
||||
|
||||
public interface IAuthenticationService
|
||||
{
|
||||
Task<User?> GetUserAsync();
|
||||
Task<bool> GetAuthenticationStatusAsync();
|
||||
Task LogoutAsync();
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using WatchIt.Common.Model.Accounts;
|
||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||
|
||||
namespace WatchIt.Website.Services.Utility.Tokens;
|
||||
|
||||
public class JWTAuthenticationStateProvider : AuthenticationStateProvider
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
private readonly ILogger<JWTAuthenticationStateProvider> _logger;
|
||||
|
||||
private readonly ITokensService _tokensService;
|
||||
private readonly IAccountsWebAPIService _accountsService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public JWTAuthenticationStateProvider(HttpClient httpClient, ILogger<JWTAuthenticationStateProvider> logger, ITokensService tokensService, IAccountsWebAPIService accountsService)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
|
||||
_logger = logger;
|
||||
|
||||
_tokensService = tokensService;
|
||||
_accountsService = accountsService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||
{
|
||||
AuthenticationState state = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
|
||||
|
||||
Task<string?> accessTokenTask = _tokensService.GetAccessToken();
|
||||
Task<string?> refreshTokenTask = _tokensService.GetRefreshToken();
|
||||
await Task.WhenAll(accessTokenTask, refreshTokenTask);
|
||||
string? accessToken = await accessTokenTask;
|
||||
string? refreshToken = await refreshTokenTask;
|
||||
|
||||
bool refreshed = false;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(accessToken))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(refreshToken))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
string? accessTokenNew = await Refresh(refreshToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(accessToken))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
accessToken = accessTokenNew;
|
||||
refreshed = true;
|
||||
}
|
||||
|
||||
IEnumerable<Claim> claims = GetClaimsFromToken(accessToken);
|
||||
|
||||
Claim? expClaim = claims.FirstOrDefault(c => c.Type == "exp");
|
||||
|
||||
if (expClaim is not null && ConvertFromUnixTimestamp(int.Parse(expClaim.Value)) > DateTime.UtcNow)
|
||||
{
|
||||
if (refreshed)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(refreshToken))
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
string? accessTokenNew = await Refresh(refreshToken);
|
||||
|
||||
if (accessTokenNew is null)
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
accessToken = accessTokenNew;
|
||||
}
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.Replace("\"", ""));
|
||||
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private async Task<string?> Refresh(string refreshToken)
|
||||
{
|
||||
AuthenticateResponse response = null;
|
||||
|
||||
await _accountsService.AuthenticateRefresh((data) => response = data);
|
||||
|
||||
await _tokensService.SaveAuthenticationData(response);
|
||||
|
||||
return response.AccessToken;
|
||||
}
|
||||
|
||||
private static IEnumerable<Claim> GetClaimsFromToken(string token)
|
||||
{
|
||||
string payload = token.Split('.')[1];
|
||||
|
||||
switch (payload.Length % 4)
|
||||
{
|
||||
case 2: payload += "=="; break;
|
||||
case 3: payload += "="; break;
|
||||
}
|
||||
|
||||
byte[] jsonBytes = Convert.FromBase64String(payload);
|
||||
Dictionary<string, object>? keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
|
||||
|
||||
if (keyValuePairs is null)
|
||||
{
|
||||
throw new Exception("Incorrect token");
|
||||
}
|
||||
|
||||
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
|
||||
}
|
||||
|
||||
public static DateTime ConvertFromUnixTimestamp(int timestamp)
|
||||
{
|
||||
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
|
||||
return origin.AddSeconds(timestamp);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace WatchIt.Website.Services.Utility.Authentication;
|
||||
|
||||
public class User
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
public required long Id { get; init; }
|
||||
public required string Username { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public required bool IsAdmin { get; init; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Accounts\WatchIt.Website.Services.WebAPI.Accounts.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services.Utility.Tokens\WatchIt.Website.Services.Utility.Tokens.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -6,4 +6,6 @@ public class Accounts
|
||||
public string Register { get; set; }
|
||||
public string Authenticate { get; set; }
|
||||
public string AuthenticateRefresh { get; set; }
|
||||
public string Logout { get; set; }
|
||||
public string GetProfilePicture { get; set; }
|
||||
}
|
||||
@@ -4,5 +4,6 @@ public class ConfigurationData
|
||||
{
|
||||
public Logging Logging { get; set; }
|
||||
public string AllowedHosts { get; set; }
|
||||
public StorageKeys StorageKeys { get; set; }
|
||||
public Endpoints Endpoints { get; set; }
|
||||
}
|
||||
@@ -8,7 +8,4 @@ public class Movies
|
||||
public string Post { get; set; }
|
||||
public string Put { get; set; }
|
||||
public string Delete { get; set; }
|
||||
public string GetGenres { get; set; }
|
||||
public string PostGenre { get; set; }
|
||||
public string DeleteGenre { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace WatchIt.Website.Services.Utility.Configuration.Model;
|
||||
|
||||
public class StorageKeys
|
||||
{
|
||||
public string AccessToken { get; set; }
|
||||
public string RefreshToken { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using WatchIt.Common.Model.Accounts;
|
||||
|
||||
namespace WatchIt.Website.Services.Utility.Tokens;
|
||||
|
||||
public interface ITokensService
|
||||
{
|
||||
Task<string?> GetAccessToken();
|
||||
Task<string?> GetRefreshToken();
|
||||
Task SaveAuthenticationData(AuthenticateResponse authenticateResponse);
|
||||
Task SaveAccessToken(string accessToken);
|
||||
Task SaveRefreshToken(string refreshToken);
|
||||
Task RemoveAuthenticationData();
|
||||
Task RemoveAccessToken();
|
||||
Task RemoveRefreshToken();
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
|
||||
using WatchIt.Common.Model.Accounts;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
|
||||
namespace WatchIt.Website.Services.Utility.Tokens;
|
||||
|
||||
public class TokensService : ITokensService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private readonly ProtectedLocalStorage _localStorageService;
|
||||
private readonly IConfigurationService _configurationService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public TokensService(ProtectedLocalStorage localStorageService, IConfigurationService configurationService)
|
||||
{
|
||||
_localStorageService = localStorageService;
|
||||
_configurationService = configurationService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task<string?> GetAccessToken() => await GetValueAsync<string>(GetAccessTokenStorageKey());
|
||||
|
||||
public async Task<string?> GetRefreshToken() => await GetValueAsync<string>(GetRefreshTokenStorageKey());
|
||||
|
||||
public async Task SaveAuthenticationData(AuthenticateResponse authenticateResponse) => await Task.WhenAll(SaveAccessToken(authenticateResponse.AccessToken), SaveRefreshToken(authenticateResponse.RefreshToken));
|
||||
|
||||
public async Task SaveAccessToken(string accessToken) => await _localStorageService.SetAsync(GetAccessTokenStorageKey(), accessToken);
|
||||
|
||||
public async Task SaveRefreshToken(string refreshToken) => await _localStorageService.SetAsync(GetRefreshTokenStorageKey(), refreshToken);
|
||||
|
||||
public async Task RemoveAuthenticationData() => await Task.WhenAll(RemoveAccessToken(), RemoveRefreshToken());
|
||||
|
||||
public async Task RemoveAccessToken() => await _localStorageService.DeleteAsync(GetAccessTokenStorageKey());
|
||||
|
||||
public async Task RemoveRefreshToken() => await _localStorageService.DeleteAsync(GetRefreshTokenStorageKey());
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private string GetAccessTokenStorageKey() => _configurationService.Data.StorageKeys.AccessToken;
|
||||
private string GetRefreshTokenStorageKey() => _configurationService.Data.StorageKeys.RefreshToken;
|
||||
|
||||
private async Task<T?> GetValueAsync<T>(string key)
|
||||
{
|
||||
ProtectedBrowserStorageResult<T> result = await _localStorageService.GetAsync<T>(key);
|
||||
return result.Success ? result.Value : default;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>WatchIt.Website.Services.Utility.Token</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -2,15 +2,16 @@
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.Utility.Configuration.Model;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Accounts;
|
||||
|
||||
public class AccountsWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : BaseWebAPIService(configurationService), IAccountsWebAPIService
|
||||
public class AccountsWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService, ITokensService tokensService) : BaseWebAPIService(configurationService), IAccountsWebAPIService
|
||||
{
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task Register(RegisterRequest data, Action<RegisterResponse> createdAction, Action<IDictionary<string, string[]>> badRequestAction)
|
||||
public async Task Register(RegisterRequest data, Action<RegisterResponse>? createdAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.Register);
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
|
||||
@@ -24,7 +25,7 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse> successAction, Action<IDictionary<string, string[]>> badRequestAction, Action unauthorizedAction)
|
||||
public async Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.Authenticate);
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
|
||||
@@ -39,10 +40,13 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task AuthenticateRefresh(Action<AuthenticateResponse> successAction, Action unauthorizedAction, Action forbiddenAction)
|
||||
public async Task AuthenticateRefresh(Action<AuthenticateResponse>? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.AuthenticateRefresh);
|
||||
string? token = await tokensService.GetRefreshToken();
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||
request.Headers.Add("Authorization", $"Bearer {token}");
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
@@ -50,6 +54,31 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task Logout(Action? successAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.Logout);
|
||||
string? token = await tokensService.GetRefreshToken();
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
request.Headers.Add("Authorization", $"Bearer {token}");
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Accounts.GetProfilePicture, id);
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ namespace WatchIt.Website.Services.WebAPI.Accounts;
|
||||
|
||||
public interface IAccountsWebAPIService
|
||||
{
|
||||
Task Register(RegisterRequest data, Action<RegisterResponse> createdAction, Action<IDictionary<string, string[]>> badRequestAction);
|
||||
Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse> successAction, Action<IDictionary<string, string[]>> badRequestAction, Action unauthorizedAction);
|
||||
Task AuthenticateRefresh(Action<AuthenticateResponse> successAction, Action unauthorizedAction, Action forbiddenAction);
|
||||
Task Register(RegisterRequest data, Action<RegisterResponse>? createdAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null);
|
||||
Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null);
|
||||
Task AuthenticateRefresh(Action<AuthenticateResponse>? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task Logout(Action? successAction = null);
|
||||
Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
public enum AuthorizationType
|
||||
{
|
||||
Access,
|
||||
Refresh
|
||||
}
|
||||
@@ -1,13 +1,33 @@
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.Utility.Configuration.Model;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
public abstract class BaseWebAPIService(IConfigurationService configurationService)
|
||||
public abstract class BaseWebAPIService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
protected readonly IConfigurationService _configurationService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
protected Endpoints EndpointsConfiguration => configurationService.Data.Endpoints;
|
||||
protected Endpoints EndpointsConfiguration => _configurationService.Data.Endpoints;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
protected BaseWebAPIService(IConfigurationService configurationService)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Configuration\WatchIt.WebAPI.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Tokens\WatchIt.Website.Services.Utility.Tokens.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace WatchIt.Website.Services.WebAPI.Media;
|
||||
|
||||
public interface IMediaWebAPIService
|
||||
{
|
||||
Task GetGenres(long mediaId, Action<IEnumerable<GenreResponse>> successAction, Action notFoundAction);
|
||||
Task PostGenre(long mediaId, long genreId, Action successAction, Action unauthorizedAction, Action forbiddenAction, Action notFoundAction);
|
||||
Task GetPhotoRandomBackground(Action<MediaPhotoResponse> successAction, Action notFoundAction);
|
||||
Task GetGenres(long mediaId, Action<IEnumerable<GenreResponse>>? successAction = null, Action? notFoundAction = null);
|
||||
Task PostGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
|
||||
Task GetPhotoRandomBackground(Action<MediaPhotoResponse>? successAction = null, Action? notFoundAction = null);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using WatchIt.Common.Model.Movies;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Movies;
|
||||
|
||||
public interface IMoviesWebAPIService
|
||||
{
|
||||
Task GetAll(MovieQueryParameters? query = null, Action<IEnumerable<MovieResponse>>? successAction = null);
|
||||
Task Post(MovieRequest data, Action<MovieResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task Get(long id, Action<MovieResponse>? successAction = null, Action? notFoundAction = null);
|
||||
Task Put(long id, MovieRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
Task Delete(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.WebAPI.Common;
|
||||
|
||||
namespace WatchIt.Website.Services.WebAPI.Movies;
|
||||
|
||||
public class MoviesWebAPIService : BaseWebAPIService, IMoviesWebAPIService
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
private IHttpClientService _httpClientService;
|
||||
private IConfigurationService _configurationService;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region CONSTRUCTORS
|
||||
|
||||
public MoviesWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : base(configurationService)
|
||||
{
|
||||
_httpClientService = httpClientService;
|
||||
_configurationService = configurationService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region PUBLIC METHODS
|
||||
|
||||
public async Task GetAll(MovieQueryParameters? query = null, Action<IEnumerable<MovieResponse>>? successAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.GetAll);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
request.Query = query;
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task Post(MovieRequest data, Action<MovieResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.Post);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||
request.Body = data;
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task Get(long id, Action<MovieResponse>? successAction = null, Action? notFoundAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.Get, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor404NotFound(notFoundAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task Put(long id, MovieRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.Put, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Put, url);
|
||||
request.Body = data;
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor400BadRequest(badRequestAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
public async Task Delete(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null)
|
||||
{
|
||||
string url = GetUrl(EndpointsConfiguration.Movies.Delete, id);
|
||||
|
||||
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||
|
||||
HttpResponse response = await _httpClientService.SendRequestAsync(request);
|
||||
response.RegisterActionFor2XXSuccess(successAction)
|
||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||
.RegisterActionFor403Forbidden(forbiddenAction)
|
||||
.ExecuteAction();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override string GetServiceBase() => EndpointsConfiguration.Movies.Base;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -6,4 +6,18 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Components">
|
||||
<HintPath>..\..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.2\Microsoft.AspNetCore.Components.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Configuration\WatchIt.WebAPI.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services.WebAPI.Common\WatchIt.Website.Services.WebAPI.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<base href="/"/>
|
||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="app.css?version=0.2"/>
|
||||
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.2"/>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="app.css?version=0.7"/>
|
||||
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.7"/>
|
||||
|
||||
<!-- BOOTSTRAP -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
|
||||
<!-- FONTS -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Belanosima:wght@400;600;700&family=PT+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
||||
|
||||
<HeadOutlet @rendermode="InteractiveServer"/>
|
||||
</head>
|
||||
|
||||
|
||||
67
WatchIt.Website/WatchIt.Website/Components/MediaForm.razor
Normal file
67
WatchIt.Website/WatchIt.Website/Components/MediaForm.razor
Normal file
@@ -0,0 +1,67 @@
|
||||
<div class="row">
|
||||
<div class="col-auto rounded-3 panel panel-regular m-1">
|
||||
<img class="rounded-2 m-2 mt-3 shadow" src="@(_posterBase64 is not null ? $"data:{_posterMediaType};base64,{_posterBase64}" : "assets/poster.png")" alt="poster" width="300" height="500"/>
|
||||
<br/>
|
||||
<InputFile id="posterInput" class="m-2 form-control" OnChange="LoadPoster" disabled=@(!Id.HasValue) autocomplete="off"/>
|
||||
@if (_posterChanged)
|
||||
{
|
||||
<button id="posterButton" type="button" class="btn btn-secondary m-2 mb-3 form-control" @onclick="SavePoster" disabled=@(!Id.HasValue) autocomplete="off">Save poster</button>
|
||||
}
|
||||
</div>
|
||||
<div class="col rounded-3 panel panel-regular m-1 p-3">
|
||||
<EditForm Model="Data" FormName="MediaInfo">
|
||||
<AntiforgeryToken/>
|
||||
<div class="form-group row my-1">
|
||||
<label for="title" class="col-2 col-form-label">Title*</label>
|
||||
<div class="col-10">
|
||||
<InputText id="title" class="form-control" @bind-Value="Data!.Title"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row my-1">
|
||||
<label for="originalTitle" class="col-2 col-form-label">Original title</label>
|
||||
<div class="col-10">
|
||||
<InputText id="originalTitle" class="form-control" @bind-Value="Data!.OriginalTitle"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row my-1">
|
||||
<label for="description" class="col-2 col-form-label">Description</label>
|
||||
<div class="col-10">
|
||||
<InputTextArea id="description" class="form-control" rows="14" @bind-Value="Data!.Description"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row my-1">
|
||||
<label for="releaseDate" class="col-2 col-form-label">Release date</label>
|
||||
<div class="col-10">
|
||||
<InputDate TValue="DateOnly?" id="releaseDate" class="form-control" @bind-Value="Data!.ReleaseDate"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row my-1">
|
||||
<label for="length" class="col-2 col-form-label">Length</label>
|
||||
<div class="col-10">
|
||||
<InputNumber TValue="short?" id="length" class="form-control" @bind-Value="Data!.Length"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-1 mt-3">
|
||||
<div class="col d-flex align-items-center">
|
||||
@if (SaveDataErrors is not null && SaveDataErrors.Any())
|
||||
{
|
||||
<p class="text-danger m-0">@(SaveDataErrors.ElementAt(0))</p>
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(SaveDataInfo))
|
||||
{
|
||||
<p class="text-success m-0">@(SaveDataInfo)</p>
|
||||
}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-secondary" @onclick="SaveDataAction">Save data</button>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#posterInput, #posterButton {
|
||||
width: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using WatchIt.Common.Model.Media;
|
||||
|
||||
namespace WatchIt.Website.Components;
|
||||
|
||||
public partial class MediaForm : ComponentBase
|
||||
{
|
||||
#region PROPERTIES
|
||||
|
||||
[Parameter] public Media Data { get; set; }
|
||||
[Parameter] public long? Id { get; set; }
|
||||
[Parameter] public Func<Task> SaveDataAction { get; set; }
|
||||
[Parameter] public IEnumerable<string>? SaveDataErrors { get; set; }
|
||||
[Parameter] public string? SaveDataInfo { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private string? _actualPosterBase64 = null;
|
||||
private string? _actualPosterMediaType = null;
|
||||
private bool _posterChanged = false;
|
||||
private string? _posterBase64 = null;
|
||||
private string? _posterMediaType = null;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
private async Task LoadPoster(InputFileChangeEventArgs args)
|
||||
{
|
||||
if (args.File.ContentType.StartsWith("image"))
|
||||
{
|
||||
Stream stream = args.File.OpenReadStream(5242880);
|
||||
byte[] array;
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
await stream.CopyToAsync(ms);
|
||||
array = ms.ToArray();
|
||||
}
|
||||
|
||||
_posterMediaType = args.File.ContentType;
|
||||
_posterBase64 = Convert.ToBase64String(array);
|
||||
_posterChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SavePoster()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#posterInput {
|
||||
width: 300px;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="row">
|
||||
<div class="col rounded-3 panel panel-regular m-1">
|
||||
<div>
|
||||
<h2 class="text-danger">Sorry. You have no permission to view this site.</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,36 +1,47 @@
|
||||
@using System.Diagnostics
|
||||
@using System.Text
|
||||
@using WatchIt.Common.Model.Media
|
||||
@using WatchIt.Website.Services.WebAPI.Media
|
||||
@inherits LayoutComponentBase
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@if (loaded)
|
||||
@if (_loaded)
|
||||
{
|
||||
<div class="container-xl">
|
||||
<div class="row align-items-center m-2 rounded-3 header panel">
|
||||
<div class="col-sm-4">
|
||||
<div class="row align-items-center m-1 my-2 rounded-3 header panel panel-header z-3">
|
||||
<div class="col-2">
|
||||
<a class="logo" href="/">
|
||||
WatchIt
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="col">
|
||||
<p>Menu</p>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="col-auto">
|
||||
<div class="d-flex flex-row-reverse">
|
||||
@if (signedIn)
|
||||
@if (_user is null)
|
||||
{
|
||||
<p>test</p>
|
||||
<a class="main-button" href="/auth">Sign in</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="main-button" href="/auth">Sign in or up</a>
|
||||
<div class="dropdown z-3">
|
||||
<a class="dropdown-toggle align-items-center text-decoration-none d-flex" id="dropdownUser" aria-expanded="false" @onclick="() => _userMenuIsActive = !_userMenuIsActive">
|
||||
<img class="rounded-circle" alt="avatar" height="30" src="@(_userProfilePicture)"/>
|
||||
<div class="text-decoration-none mx-2 text-white">@(_user.Username)</div>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-right text-small z-3" id="user-menu" aria-labelledby="dropdownUser">
|
||||
<li>
|
||||
@if (_user.IsAdmin)
|
||||
{
|
||||
<a class="dropdown-item" href="/admin">Administrator panel</a>
|
||||
}
|
||||
<div class="dropdown-menu-separator"></div>
|
||||
<a class="dropdown-item text-danger" @onclick="UserMenuLogOut">Log out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row body-content">
|
||||
<div class="col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col z-0">
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,11 +49,16 @@
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: url('@background') no-repeat center center fixed;
|
||||
background: url('@_background') no-repeat center center fixed;
|
||||
}
|
||||
|
||||
.logo, .main-button {
|
||||
background-image: linear-gradient(45deg, @firstGradientColor, @secondGradientColor);
|
||||
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
||||
}
|
||||
|
||||
#user-menu {
|
||||
display: @(_userMenuIsActive ? "block" : "none");
|
||||
position: fixed;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@@ -55,6 +71,10 @@
|
||||
#region SERVICES
|
||||
|
||||
[Inject] public ILogger<MainLayout> Logger { get; set; } = default!;
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] public ITokensService TokensService { get; set; } = default!;
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
[Inject] public IAccountsWebAPIService AccountsWebAPIService { get; set; } = default!;
|
||||
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
@@ -63,12 +83,15 @@
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool loaded = false;
|
||||
private bool _loaded = false;
|
||||
|
||||
private string background = "assets/background_temp.jpg";
|
||||
private string firstGradientColor = "#c6721c";
|
||||
private string secondGradientColor = "#85200c";
|
||||
private bool signedIn = false;
|
||||
private string _background = "assets/background_temp.jpg";
|
||||
private string _firstGradientColor = "#c6721c";
|
||||
private string _secondGradientColor = "#85200c";
|
||||
|
||||
private User? _user = null;
|
||||
private string _userProfilePicture = "assets/user_placeholder.png";
|
||||
private bool _userMenuIsActive = false;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -76,7 +99,29 @@
|
||||
|
||||
#region METHODS
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
List<Task> bgTasks = new List<Task>();
|
||||
|
||||
bgTasks.Add(GetBackground());
|
||||
|
||||
await GetAuthenticatedUser();
|
||||
|
||||
if (_user is not null)
|
||||
{
|
||||
bgTasks.Add(GetProfilePicture());
|
||||
}
|
||||
|
||||
await Task.WhenAll(bgTasks);
|
||||
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetBackground()
|
||||
{
|
||||
Action<MediaPhotoResponse> backgroundSuccess = (data) =>
|
||||
{
|
||||
@@ -86,13 +131,33 @@
|
||||
string secondColor = BitConverter.ToString(data.Background.SecondGradientColor)
|
||||
.Replace("-", string.Empty);
|
||||
|
||||
background = $"data:{data.MimeType};base64,{imageBase64}";
|
||||
firstGradientColor = $"#{firstColor}";
|
||||
secondGradientColor = $"#{secondColor}";
|
||||
_background = $"data:{data.MimeType};base64,{imageBase64}";
|
||||
_firstGradientColor = $"#{firstColor}";
|
||||
_secondGradientColor = $"#{secondColor}";
|
||||
};
|
||||
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess, null);
|
||||
|
||||
loaded = true;
|
||||
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess);
|
||||
}
|
||||
|
||||
private async Task GetAuthenticatedUser()
|
||||
{
|
||||
_user = await AuthenticationService.GetUserAsync();
|
||||
}
|
||||
|
||||
private async Task GetProfilePicture()
|
||||
{
|
||||
Action<AccountProfilePictureResponse> successAction = (data) =>
|
||||
{
|
||||
string imageBase64 = Convert.ToBase64String(data.Image);
|
||||
_userProfilePicture = $"data:{data.MimeType};base64,{imageBase64}";
|
||||
};
|
||||
await AccountsWebAPIService.GetAccountProfilePicture(_user.Id, successAction);
|
||||
}
|
||||
|
||||
private async Task UserMenuLogOut()
|
||||
{
|
||||
await AuthenticationService.LogoutAsync();
|
||||
await TokensService.RemoveAuthenticationData();
|
||||
NavigationManager.Refresh(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
56
WatchIt.Website/WatchIt.Website/Pages/Admin.razor
Normal file
56
WatchIt.Website/WatchIt.Website/Pages/Admin.razor
Normal file
@@ -0,0 +1,56 @@
|
||||
@page "/admin"
|
||||
|
||||
<PageTitle>WatchIt administrator panel</PageTitle>
|
||||
|
||||
@if (_loaded)
|
||||
{
|
||||
<div class="container-fluid">
|
||||
@if (_authenticated)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col rounded-3 panel panel-regular m-1">
|
||||
<h2>Add new data</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a class="col rounded-3 panel panel-regular m-1" href="/movies/new">
|
||||
<p class="text-center text-decorations-none">New movie</p>
|
||||
</a>
|
||||
|
||||
<div class="col rounded-3 panel panel-regular m-1">
|
||||
<p class="text-center">New TV series</p>
|
||||
</div>
|
||||
<div class="col rounded-3 panel panel-regular m-1">
|
||||
<p class="text-center">New TV series</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<NoPermissionComponent/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
|
||||
private bool _loaded = false;
|
||||
private bool _authenticated = false;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
User? user = await AuthenticationService.GetUserAsync();
|
||||
if (user is not null && user.IsAdmin)
|
||||
{
|
||||
_authenticated = true;
|
||||
}
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
WatchIt.Website/WatchIt.Website/Pages/Admin.razor.css
Normal file
1
WatchIt.Website/WatchIt.Website/Pages/Admin.razor.css
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
@page "/auth"
|
||||
@using System.Text
|
||||
@using WatchIt.Common.Model.Accounts
|
||||
@using WatchIt.Common.Model.Media
|
||||
@using WatchIt.Website.Services.Utility.Authentication
|
||||
@using WatchIt.Website.Services.Utility.Tokens
|
||||
@using WatchIt.Website.Services.WebAPI.Accounts
|
||||
@using WatchIt.Website.Services.WebAPI.Media
|
||||
@layout EmptyLayout
|
||||
|
||||
<PageTitle>WatchIt - @(_authType == AuthType.SignIn ? "Sign in" : "Sign up")</PageTitle>
|
||||
@@ -6,38 +13,129 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="h-100 d-flex align-items-center justify-content-center">
|
||||
<div class="d-inline-flex flex-column justify-content-center panel rounded-3">
|
||||
<a class="logo" href="/">
|
||||
WatchIt
|
||||
</a>
|
||||
@if (_authType == AuthType.SignIn)
|
||||
{
|
||||
|
||||
}
|
||||
@if (_loaded)
|
||||
{
|
||||
<div class="h-100 d-flex align-items-center justify-content-center">
|
||||
<div class="d-inline-flex flex-column justify-content-center panel panel-header rounded-3">
|
||||
<a class="logo" href="/">
|
||||
WatchIt
|
||||
</a>
|
||||
<div>
|
||||
@if (_authType == AuthType.SignIn)
|
||||
{
|
||||
<form method="post" @onsubmit="Login" @formname="login">
|
||||
<AntiforgeryToken/>
|
||||
<div>
|
||||
<label>
|
||||
Username or email:
|
||||
<InputText @bind-Value="_loginModel!.UsernameOrEmail"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Password:
|
||||
<InputText type="password" @bind-Value="_loginModel!.Password"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
<InputCheckbox @bind-Value="_loginModel!.RememberMe"></InputCheckbox>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" @onsubmit="Register" @formname="register">
|
||||
<AntiforgeryToken/>
|
||||
<div>
|
||||
<label>
|
||||
Username:
|
||||
<InputText @bind-Value="_registerModel!.Username"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Email:
|
||||
<InputText @bind-Value="_registerModel!.Email"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Password:
|
||||
<InputText type="password" @bind-Value="_registerModel!.Password"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
Confirm password:
|
||||
<InputText type="password" @bind-Value="_passwordConfirmation"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Sign up</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
@if (_errors is not null)
|
||||
{
|
||||
<div class="text-danger">
|
||||
@foreach (string error in _errors)
|
||||
{
|
||||
@error
|
||||
<br/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<label>
|
||||
<input type="radio" checked="@(() => _authType == AuthType.SignIn)" name="auth" @onchange="@(() => _authType = AuthType.SignIn)" />
|
||||
Sign in
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" checked="@(() => _authType == AuthType.SignUp)" name="auth" @onchange="@(() => _authType = AuthType.SignUp)" />
|
||||
Sign up
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: url('@(_background)') no-repeat center center fixed;
|
||||
}
|
||||
|
||||
.logo {
|
||||
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: url('@(_background)') no-repeat center center fixed;
|
||||
}
|
||||
@code
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
.logo {
|
||||
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
||||
}
|
||||
</style>
|
||||
[Inject] public ILogger<Auth> Logger { get; set; } = default!;
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
[Inject] public ITokensService TokensService { get; set; } = default!;
|
||||
[Inject] public IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||
[Inject] public IAccountsWebAPIService AccountsWebAPIService { get; set; } = default!;
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@code {
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region ENUMS
|
||||
|
||||
@@ -52,21 +150,100 @@
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded = false;
|
||||
|
||||
private AuthType _authType = AuthType.SignIn;
|
||||
private string _background = "assets/background_temp.jpg";
|
||||
private string _firstGradientColor = "#c6721c";
|
||||
private string _secondGradientColor = "#85200c";
|
||||
|
||||
private AuthenticateRequest _loginModel = new AuthenticateRequest
|
||||
{
|
||||
UsernameOrEmail = null,
|
||||
Password = null
|
||||
};
|
||||
|
||||
private RegisterRequest _registerModel = new RegisterRequest
|
||||
{
|
||||
Username = null,
|
||||
Email = null,
|
||||
Password = null
|
||||
};
|
||||
private string _passwordConfirmation;
|
||||
|
||||
private IEnumerable<string> _errors;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region METHODS
|
||||
|
||||
protected override Task OnInitializedAsync()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
return base.OnInitializedAsync();
|
||||
if (await AuthenticationService.GetAuthenticationStatusAsync())
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
|
||||
Action<MediaPhotoResponse> backgroundSuccess = (data) =>
|
||||
{
|
||||
string imageBase64 = Convert.ToBase64String(data.Image);
|
||||
string firstColor = BitConverter.ToString(data.Background.FirstGradientColor)
|
||||
.Replace("-", string.Empty);
|
||||
string secondColor = BitConverter.ToString(data.Background.SecondGradientColor)
|
||||
.Replace("-", string.Empty);
|
||||
|
||||
_background = $"data:{data.MimeType};base64,{imageBase64}";
|
||||
_firstGradientColor = $"#{firstColor}";
|
||||
_secondGradientColor = $"#{secondColor}";
|
||||
};
|
||||
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess);
|
||||
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
private async Task Login()
|
||||
{
|
||||
await AccountsWebAPIService.Authenticate(_loginModel, LoginSuccess, LoginBadRequest, LoginUnauthorized);
|
||||
|
||||
async void LoginSuccess(AuthenticateResponse data)
|
||||
{
|
||||
await TokensService.SaveAuthenticationData(data);
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
|
||||
void LoginBadRequest(IDictionary<string, string[]> data)
|
||||
{
|
||||
_errors = data.SelectMany(x => x.Value).Select(x => $"• {x}");
|
||||
}
|
||||
|
||||
void LoginUnauthorized()
|
||||
{
|
||||
_errors = [ "Incorrect account data" ];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Register()
|
||||
{
|
||||
if (_registerModel.Password != _passwordConfirmation)
|
||||
{
|
||||
_errors = [ "Password fields don't match" ];
|
||||
return;
|
||||
}
|
||||
|
||||
await AccountsWebAPIService.Register(_registerModel, RegisterSuccess, RegisterBadRequest);
|
||||
|
||||
void RegisterSuccess(RegisterResponse data)
|
||||
{
|
||||
_authType = AuthType.SignIn;
|
||||
}
|
||||
|
||||
void RegisterBadRequest(IDictionary<string, string[]> data)
|
||||
{
|
||||
_errors = data.SelectMany(x => x.Value).Select(x => $"• {x}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -5,46 +5,50 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
<h2>Hello, world!</h2>
|
||||
<p>Welcome to your new app.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
23
WatchIt.Website/WatchIt.Website/Pages/MovieEditPage.razor
Normal file
23
WatchIt.Website/WatchIt.Website/Pages/MovieEditPage.razor
Normal file
@@ -0,0 +1,23 @@
|
||||
@page "/movies/new"
|
||||
@page "/movies/{id:long}/edit"
|
||||
|
||||
<PageTitle>WatchIt - New movie</PageTitle>
|
||||
|
||||
@if (_loaded)
|
||||
{
|
||||
<div class="container-fluid">
|
||||
@if (_authenticated)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col rounded-3 panel panel-regular m-1">
|
||||
<h2>@(Id is null ? "Create new movie" : $"Edit movie \"{_movieInfo.Title}\"")</h2>
|
||||
</div>
|
||||
</div>
|
||||
<MediaForm Data=@(_movieData) SaveDataAction=SaveData Id=@(Id) SaveDataErrors=@(_movieDataErrors) SaveDataInfo=@(_movieDataInfo)/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<NoPermissionComponent/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
102
WatchIt.Website/WatchIt.Website/Pages/MovieEditPage.razor.cs
Normal file
102
WatchIt.Website/WatchIt.Website/Pages/MovieEditPage.razor.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using WatchIt.Common.Model.Movies;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.WebAPI.Movies;
|
||||
|
||||
namespace WatchIt.Website.Pages;
|
||||
|
||||
public partial class MovieEditPage : ComponentBase
|
||||
{
|
||||
#region SERVICES
|
||||
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||
[Inject] public IMoviesWebAPIService MoviesWebAPIService { get; set; } = default!;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PARAMETERS
|
||||
|
||||
[Parameter]
|
||||
public long? Id { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region FIELDS
|
||||
|
||||
private bool _loaded = false;
|
||||
private bool _authenticated = false;
|
||||
|
||||
private MovieResponse? _movieInfo = null;
|
||||
|
||||
private MovieRequest _movieData = new MovieRequest { Title = string.Empty };
|
||||
private IEnumerable<string>? _movieDataErrors = null;
|
||||
private string? _movieDataInfo = null;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
User? user = await AuthenticationService.GetUserAsync();
|
||||
if (user is not null && user.IsAdmin)
|
||||
{
|
||||
_authenticated = true;
|
||||
|
||||
await LoadData();
|
||||
}
|
||||
_loaded = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
if (Id is not null)
|
||||
{
|
||||
await MoviesWebAPIService.Get(Id.Value, GetSuccessAction, NoIdAction);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void GetSuccessAction(MovieResponse data)
|
||||
{
|
||||
_movieInfo = data;
|
||||
_movieData = new MovieRequest(_movieInfo);
|
||||
}
|
||||
void NoIdAction() => NavigationManager.NavigateTo("/movies/new", true); // await for all
|
||||
}
|
||||
|
||||
private async Task SaveData()
|
||||
{
|
||||
_movieDataErrors = null;
|
||||
_movieDataInfo = null;
|
||||
if (Id is null)
|
||||
{
|
||||
await MoviesWebAPIService.Post(_movieData, PostSuccessAction, BadRequestAction, NoPermissionsAction, NoPermissionsAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
await MoviesWebAPIService.Put(Id.Value, _movieData, PutSuccessAction, BadRequestAction, NoPermissionsAction, NoPermissionsAction);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void PutSuccessAction() => _movieDataInfo = "Data saved";
|
||||
void PostSuccessAction(MovieResponse data) => NavigationManager.NavigateTo($"/movies/{data.Id}/edit", true);
|
||||
void BadRequestAction(IDictionary<string, string[]> errors) => _movieDataErrors = errors.SelectMany(x => x.Value);
|
||||
void NoPermissionsAction() => NavigationManager.Refresh(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using WatchIt.Common.Services.HttpClient;
|
||||
using WatchIt.Website.Services.Utility.Authentication;
|
||||
using WatchIt.Website.Services.Utility.Configuration;
|
||||
using WatchIt.Website.Services.Utility.Tokens;
|
||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||
using WatchIt.Website.Services.WebAPI.Media;
|
||||
using WatchIt.Website.Services.WebAPI.Movies;
|
||||
|
||||
namespace WatchIt.Website;
|
||||
|
||||
@@ -13,6 +19,7 @@ public static class Program
|
||||
{
|
||||
WebApplication app = WebApplication.CreateBuilder(args)
|
||||
.SetupServices()
|
||||
.SetupAuthentication()
|
||||
.SetupApplication()
|
||||
.Build();
|
||||
|
||||
@@ -40,18 +47,29 @@ public static class Program
|
||||
|
||||
|
||||
#region PRIVATE METHODS
|
||||
|
||||
|
||||
private static WebApplicationBuilder SetupServices(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddSingleton<HttpClient>();
|
||||
|
||||
// Utility
|
||||
builder.Services.AddSingleton<IHttpClientService, HttpClientService>();
|
||||
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
|
||||
builder.Services.AddScoped<ITokensService, TokensService>();
|
||||
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
|
||||
|
||||
// WebAPI
|
||||
builder.Services.AddSingleton<IAccountsWebAPIService, AccountsWebAPIService>();
|
||||
builder.Services.AddScoped<IAccountsWebAPIService, AccountsWebAPIService>();
|
||||
builder.Services.AddSingleton<IMediaWebAPIService, MediaWebAPIService>();
|
||||
builder.Services.AddSingleton<IMoviesWebAPIService, MoviesWebAPIService>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static WebApplicationBuilder SetupAuthentication(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddAuthorizationCore();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider, JWTAuthenticationStateProvider>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" />
|
||||
<ProjectReference Include="..\..\WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Tokens\WatchIt.WebAPI.Services.Utility.Tokens.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Authentication\WatchIt.Website.Services.Utility.Authentication.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Configuration\WatchIt.Website.Services.Utility.Configuration.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Tokens\WatchIt.Website.Services.Utility.Tokens.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Accounts\WatchIt.Website.Services.WebAPI.Accounts.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Media\WatchIt.Website.Services.WebAPI.Media.csproj" />
|
||||
<ProjectReference Include="..\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Movies\WatchIt.Website.Services.WebAPI.Movies.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -27,6 +31,8 @@
|
||||
<_ContentIncludedByDefault Remove="Components\Pages\Error.razor" />
|
||||
<_ContentIncludedByDefault Remove="Components\Pages\Home.razor" />
|
||||
<_ContentIncludedByDefault Remove="Components\Pages\Weather.razor" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css.map" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -34,9 +40,4 @@
|
||||
<AdditionalFiles Include="Pages\Home.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Components\" />
|
||||
<Folder Include="wwwroot\assets\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -7,4 +7,11 @@
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using WatchIt.Website
|
||||
@using WatchIt.Website.Layout
|
||||
@using WatchIt.Website.Layout
|
||||
@using WatchIt.Website.Components
|
||||
@using WatchIt.Common.Model.Accounts
|
||||
@using WatchIt.Common.Model.Media
|
||||
@using WatchIt.Website.Services.Utility.Tokens
|
||||
@using WatchIt.Website.Services.Utility.Authentication
|
||||
@using WatchIt.Website.Services.WebAPI.Accounts
|
||||
@using WatchIt.Website.Services.WebAPI.Media
|
||||
@@ -6,13 +6,19 @@
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"StorageKeys": {
|
||||
"AccessToken": "access_token",
|
||||
"RefreshToken": "refresh_token"
|
||||
},
|
||||
"Endpoints": {
|
||||
"Base": "https://localhost:7160",
|
||||
"Accounts": {
|
||||
"Base": "/accounts",
|
||||
"Register": "/register",
|
||||
"Authenticate": "/authenticate",
|
||||
"AuthenticateRefresh": "/authenticate-refresh"
|
||||
"AuthenticateRefresh": "/authenticate-refresh",
|
||||
"Logout": "/logout",
|
||||
"GetProfilePicture": "/{0}/profile-picture"
|
||||
},
|
||||
"Genres": {
|
||||
"Base": "/genres",
|
||||
@@ -28,10 +34,7 @@
|
||||
"Get": "/{0}",
|
||||
"Post": "",
|
||||
"Put": "/{0}",
|
||||
"Delete": "/{0}",
|
||||
"GetGenres": "/{0}/genres",
|
||||
"PostGenre": "{0}/genres/{1}",
|
||||
"DeleteGenre": "{0}/genres/{1}"
|
||||
"Delete": "/{0}"
|
||||
},
|
||||
"Media": {
|
||||
"Base": "/media",
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
@font-face {
|
||||
font-family: Belanosima;
|
||||
src: url(fonts/Belanosima-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Belanosima;
|
||||
src: url(fonts/Belanosima-Bold.ttf) format('truetype');
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
body, html {
|
||||
background-color: transparent;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
color: lightgray;
|
||||
font-family: "PT Sans";
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: Belanosima;
|
||||
font-family: "Belanosima";
|
||||
text-decoration: none;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.panel {
|
||||
.panel-header {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.panel-regular {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.panel {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(25px);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.main-button {
|
||||
@@ -77,4 +75,9 @@ body, html {
|
||||
|
||||
.main-button:hover::before {
|
||||
-webkit-mask:none;
|
||||
}
|
||||
|
||||
.dropdown-menu-left {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/poster.png
Normal file
BIN
WatchIt.Website/WatchIt.Website/wwwroot/assets/poster.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 798 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user