authentication refresh fixed, movie creation page added
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace WatchIt.Common.Model.Accounts;
|
||||||
|
|
||||||
|
public abstract class AccountProfilePicture
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("image")]
|
||||||
|
public required byte[] Image { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("mime_type")]
|
||||||
|
public required string MimeType { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public AccountProfilePicture() {}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace WatchIt.Common.Model.Accounts;
|
||||||
|
|
||||||
|
public class AccountProfilePictureResponse : AccountProfilePicture
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public required Guid Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("upload_date")]
|
||||||
|
public required DateTime UploadDate { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public AccountProfilePictureResponse() {}
|
||||||
|
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
public AccountProfilePictureResponse(Database.Model.Account.AccountProfilePicture accountProfilePicture)
|
||||||
|
{
|
||||||
|
Id = accountProfilePicture.Id;
|
||||||
|
Image = accountProfilePicture.Image;
|
||||||
|
MimeType = accountProfilePicture.MimeType;
|
||||||
|
UploadDate = accountProfilePicture.UploadDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -26,6 +26,9 @@ public class RegisterResponse
|
|||||||
|
|
||||||
#region CONSTRUCTORS
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public RegisterResponse() {}
|
||||||
|
|
||||||
[SetsRequiredMembers]
|
[SetsRequiredMembers]
|
||||||
public RegisterResponse(Account account)
|
public RegisterResponse(Account account)
|
||||||
{
|
{
|
||||||
|
|||||||
21
WatchIt.Common/WatchIt.Common.Model/Media/Media.cs
Normal file
21
WatchIt.Common/WatchIt.Common.Model/Media/Media.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace WatchIt.Common.Model.Media;
|
||||||
|
|
||||||
|
public abstract class Media
|
||||||
|
{
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public required string Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("original_title")]
|
||||||
|
public string? OriginalTitle { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("description")]
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("release_date")]
|
||||||
|
public DateOnly? ReleaseDate { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("length")]
|
||||||
|
public short? Length { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace WatchIt.Common.Model.Media;
|
||||||
|
|
||||||
|
public class MediaPosterImage
|
||||||
|
{
|
||||||
|
[JsonPropertyName("image")]
|
||||||
|
public required byte[] Image { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("mime_type")]
|
||||||
|
public required string MimeType { get; set; }
|
||||||
|
}
|
||||||
@@ -2,23 +2,8 @@
|
|||||||
|
|
||||||
namespace WatchIt.Common.Model.Movies;
|
namespace WatchIt.Common.Model.Movies;
|
||||||
|
|
||||||
public class Movie
|
public class Movie : Media.Media
|
||||||
{
|
{
|
||||||
[JsonPropertyName("title")]
|
|
||||||
public required string Title { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("original_title")]
|
|
||||||
public string? OriginalTitle { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("description")]
|
|
||||||
public string? Description { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("release_date")]
|
|
||||||
public DateOnly? ReleaseDate { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("length")]
|
|
||||||
public short? Length { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("budget")]
|
[JsonPropertyName("budget")]
|
||||||
public decimal? Budget { get; set; }
|
public decimal? Budget { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,29 @@
|
|||||||
using WatchIt.Database.Model.Media;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using WatchIt.Database.Model.Media;
|
||||||
|
|
||||||
namespace WatchIt.Common.Model.Movies;
|
namespace WatchIt.Common.Model.Movies;
|
||||||
|
|
||||||
public class MovieRequest : Movie
|
public class MovieRequest : Movie
|
||||||
{
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
public MovieRequest(MovieResponse initData)
|
||||||
|
{
|
||||||
|
Title = initData.Title;
|
||||||
|
OriginalTitle = initData.OriginalTitle;
|
||||||
|
Description = initData.Description;
|
||||||
|
ReleaseDate = initData.ReleaseDate;
|
||||||
|
Length = initData.Length;
|
||||||
|
Budget = initData.Budget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieRequest() {}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
public Database.Model.Media.Media CreateMedia() => new Database.Model.Media.Media
|
public Database.Model.Media.Media CreateMedia() => new Database.Model.Media.Media
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using WatchIt.Database.Model.Media;
|
using WatchIt.Database.Model.Media;
|
||||||
|
|
||||||
namespace WatchIt.Common.Model.Movies;
|
namespace WatchIt.Common.Model.Movies;
|
||||||
@@ -7,6 +8,7 @@ public class MovieResponse : Movie
|
|||||||
{
|
{
|
||||||
#region PROPERTIES
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -15,6 +17,9 @@ public class MovieResponse : Movie
|
|||||||
|
|
||||||
#region CONSTRUCTORS
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public MovieResponse() {}
|
||||||
|
|
||||||
[SetsRequiredMembers]
|
[SetsRequiredMembers]
|
||||||
public MovieResponse(MediaMovie mediaMovie)
|
public MovieResponse(MediaMovie mediaMovie)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,4 +30,15 @@ public class AccountsController(IAccountsControllerService accountsControllerSer
|
|||||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||||
public async Task<ActionResult> AuthenticateRefresh() => await accountsControllerService.AuthenticateRefresh();
|
public async Task<ActionResult> AuthenticateRefresh() => await accountsControllerService.AuthenticateRefresh();
|
||||||
|
|
||||||
|
[HttpDelete("logout")]
|
||||||
|
[Authorize(AuthenticationSchemes = "refresh")]
|
||||||
|
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
||||||
|
public async Task<ActionResult> Logout() => await accountsControllerService.Logout();
|
||||||
|
|
||||||
|
[HttpGet("{id}/profile-picture")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ProducesResponseType(typeof(AccountProfilePictureResponse), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult> GetAccountProfilePicture([FromRoute(Name = "id")]long id) => await accountsControllerService.GetAccountProfilePicture(id);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using System.Net;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using WatchIt.Common.Model.Genres;
|
using WatchIt.Common.Model.Genres;
|
||||||
@@ -23,7 +25,7 @@ public class MoviesController(IMoviesControllerService moviesControllerService)
|
|||||||
public async Task<ActionResult> Get([FromRoute]long id) => await moviesControllerService.Get(id);
|
public async Task<ActionResult> Get([FromRoute]long id) => await moviesControllerService.Get(id);
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize]
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
[ProducesResponseType(typeof(MovieResponse), StatusCodes.Status201Created)]
|
[ProducesResponseType(typeof(MovieResponse), StatusCodes.Status201Created)]
|
||||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
@@ -31,15 +33,16 @@ public class MoviesController(IMoviesControllerService moviesControllerService)
|
|||||||
public async Task<ActionResult> Post([FromBody]MovieRequest body) => await moviesControllerService.Post(body);
|
public async Task<ActionResult> Post([FromBody]MovieRequest body) => await moviesControllerService.Post(body);
|
||||||
|
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
[Authorize]
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
|
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
||||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||||
public async Task<ActionResult> Put([FromRoute]long id, [FromBody]MovieRequest body) => await moviesControllerService.Put(id, body);
|
public async Task<ActionResult> Put([FromRoute]long id, [FromBody]MovieRequest body) => await moviesControllerService.Put(id, body);
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
[Authorize]
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
|
||||||
public async Task<ActionResult> Delete([FromRoute] long id) => await moviesControllerService.Delete(id);
|
public async Task<ActionResult> Delete([FromRoute] long id) => await moviesControllerService.Delete(id);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using WatchIt.WebAPI.Services.Controllers.Common;
|
|||||||
using WatchIt.WebAPI.Services.Utility.Tokens;
|
using WatchIt.WebAPI.Services.Utility.Tokens;
|
||||||
using WatchIt.WebAPI.Services.Utility.Tokens.Exceptions;
|
using WatchIt.WebAPI.Services.Utility.Tokens.Exceptions;
|
||||||
using WatchIt.WebAPI.Services.Utility.User;
|
using WatchIt.WebAPI.Services.Utility.User;
|
||||||
|
using AccountProfilePicture = WatchIt.Common.Model.Accounts.AccountProfilePicture;
|
||||||
|
|
||||||
namespace WatchIt.WebAPI.Services.Controllers.Accounts;
|
namespace WatchIt.WebAPI.Services.Controllers.Accounts;
|
||||||
|
|
||||||
@@ -73,16 +74,10 @@ public class AccountsControllerService(
|
|||||||
return RequestResult.Unauthorized();
|
return RequestResult.Unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticateResponse response;
|
string refreshToken;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Task<string> refreshTokenTask = tokensService.ExtendRefreshTokenAsync(token.Account, token.Id);
|
refreshToken = await tokensService.ExtendRefreshTokenAsync(token.Account, token.Id);
|
||||||
Task<string> accessTokenTask = tokensService.CreateAccessTokenAsync(token.Account);
|
|
||||||
response = new AuthenticateResponse
|
|
||||||
{
|
|
||||||
AccessToken = await accessTokenTask,
|
|
||||||
RefreshToken = await refreshTokenTask,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (TokenNotFoundException)
|
catch (TokenNotFoundException)
|
||||||
{
|
{
|
||||||
@@ -90,11 +85,48 @@ public class AccountsControllerService(
|
|||||||
}
|
}
|
||||||
catch (TokenNotExtendableException)
|
catch (TokenNotExtendableException)
|
||||||
{
|
{
|
||||||
return RequestResult.Forbidden();
|
refreshToken = userService.GetRawToken().Replace("Bearer ", string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string accessToken = await tokensService.CreateAccessTokenAsync(token.Account);
|
||||||
|
|
||||||
logger.LogInformation($"Account with ID {token.AccountId} was authenticated by token refreshing");
|
logger.LogInformation($"Account with ID {token.AccountId} was authenticated by token refreshing");
|
||||||
return RequestResult.Ok(response);
|
return RequestResult.Ok(new AuthenticateResponse
|
||||||
|
{
|
||||||
|
AccessToken = accessToken,
|
||||||
|
RefreshToken = refreshToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult> Logout()
|
||||||
|
{
|
||||||
|
Guid jti = userService.GetJti();
|
||||||
|
AccountRefreshToken? token = await database.AccountRefreshTokens.FirstOrDefaultAsync(x => x.Id == jti);
|
||||||
|
if (token is not null)
|
||||||
|
{
|
||||||
|
database.AccountRefreshTokens.Attach(token);
|
||||||
|
database.AccountRefreshTokens.Remove(token);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
return RequestResult.NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult> GetAccountProfilePicture(long id)
|
||||||
|
{
|
||||||
|
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
if (account is null)
|
||||||
|
{
|
||||||
|
return RequestResult.BadRequest()
|
||||||
|
.AddValidationError("id", "Account with this id does not exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.ProfilePicture is null)
|
||||||
|
{
|
||||||
|
return RequestResult.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountProfilePictureResponse picture = new AccountProfilePictureResponse(account.ProfilePicture);
|
||||||
|
return RequestResult.Ok(picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ public interface IAccountsControllerService
|
|||||||
Task<RequestResult> Register(RegisterRequest data);
|
Task<RequestResult> Register(RegisterRequest data);
|
||||||
Task<RequestResult> Authenticate(AuthenticateRequest data);
|
Task<RequestResult> Authenticate(AuthenticateRequest data);
|
||||||
Task<RequestResult> AuthenticateRefresh();
|
Task<RequestResult> AuthenticateRefresh();
|
||||||
|
Task<RequestResult> Logout();
|
||||||
|
Task<RequestResult> GetAccountProfilePicture(long id);
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ public class MoviesControllerService(DatabaseContext database, IUserService user
|
|||||||
data.UpdateMedia(item.Media);
|
data.UpdateMedia(item.Media);
|
||||||
await database.SaveChangesAsync();
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
return RequestResult.Ok();
|
return RequestResult.NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestResult> Delete(long id)
|
public async Task<RequestResult> Delete(long id)
|
||||||
@@ -105,7 +105,7 @@ public class MoviesControllerService(DatabaseContext database, IUserService user
|
|||||||
database.Media.Remove(item.Media);
|
database.Media.Remove(item.Media);
|
||||||
await database.SaveChangesAsync();
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
return RequestResult.Ok();
|
return RequestResult.NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -85,7 +85,9 @@ public class TokensService(DatabaseContext database, IConfigurationService confi
|
|||||||
return TokenToString(tokenDescriptor);
|
return TokenToString(tokenDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SecurityTokenDescriptor CreateBaseSecurityTokenDescriptor(Account account, Guid id, DateTime expirationTime) => new SecurityTokenDescriptor
|
protected SecurityTokenDescriptor CreateBaseSecurityTokenDescriptor(Account account, Guid id, DateTime expirationTime)
|
||||||
|
{
|
||||||
|
return new SecurityTokenDescriptor
|
||||||
{
|
{
|
||||||
Subject = new ClaimsIdentity(new List<Claim>
|
Subject = new ClaimsIdentity(new List<Claim>
|
||||||
{
|
{
|
||||||
@@ -100,6 +102,7 @@ public class TokensService(DatabaseContext database, IConfigurationService confi
|
|||||||
Issuer = configurationService.Data.Authentication.Issuer,
|
Issuer = configurationService.Data.Authentication.Issuer,
|
||||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurationService.Data.Authentication.Key)), SecurityAlgorithms.HmacSha512)
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configurationService.Data.Authentication.Key)), SecurityAlgorithms.HmacSha512)
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected string TokenToString(SecurityTokenDescriptor tokenDescriptor)
|
protected string TokenToString(SecurityTokenDescriptor tokenDescriptor)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ public class UserService(DatabaseContext database, IHttpContextAccessor accessor
|
|||||||
return accessor.HttpContext.User;
|
return accessor.HttpContext.User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string? GetRawToken()
|
||||||
|
{
|
||||||
|
if (accessor.HttpContext is null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException();
|
||||||
|
}
|
||||||
|
return accessor.HttpContext.Request.Headers.Authorization;
|
||||||
|
}
|
||||||
|
|
||||||
public UserValidator GetValidator()
|
public UserValidator GetValidator()
|
||||||
{
|
{
|
||||||
ClaimsPrincipal rawUser = GetRawUser();
|
ClaimsPrincipal rawUser = GetRawUser();
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using WatchIt.Common.Model.Movies;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Validators.Movies;
|
||||||
|
|
||||||
|
public class MovieRequestValidator : AbstractValidator<MovieRequest>
|
||||||
|
{
|
||||||
|
public MovieRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Title).NotEmpty().MaximumLength(250);
|
||||||
|
RuleFor(x => x.OriginalTitle).MaximumLength(250);
|
||||||
|
RuleFor(x => x.Description).MaximumLength(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,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 Register { get; set; }
|
||||||
public string Authenticate { get; set; }
|
public string Authenticate { get; set; }
|
||||||
public string AuthenticateRefresh { 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 Logging Logging { get; set; }
|
||||||
public string AllowedHosts { get; set; }
|
public string AllowedHosts { get; set; }
|
||||||
|
public StorageKeys StorageKeys { get; set; }
|
||||||
public Endpoints Endpoints { get; set; }
|
public Endpoints Endpoints { get; set; }
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,4 @@ public class Movies
|
|||||||
public string Post { get; set; }
|
public string Post { get; set; }
|
||||||
public string Put { get; set; }
|
public string Put { get; set; }
|
||||||
public string Delete { 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.Common.Services.HttpClient;
|
||||||
using WatchIt.Website.Services.Utility.Configuration;
|
using WatchIt.Website.Services.Utility.Configuration;
|
||||||
using WatchIt.Website.Services.Utility.Configuration.Model;
|
using WatchIt.Website.Services.Utility.Configuration.Model;
|
||||||
|
using WatchIt.Website.Services.Utility.Tokens;
|
||||||
using WatchIt.Website.Services.WebAPI.Common;
|
using WatchIt.Website.Services.WebAPI.Common;
|
||||||
|
|
||||||
namespace WatchIt.Website.Services.WebAPI.Accounts;
|
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
|
#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);
|
string url = GetUrl(EndpointsConfiguration.Accounts.Register);
|
||||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
|
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
|
||||||
@@ -24,7 +25,7 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
|
|||||||
.ExecuteAction();
|
.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);
|
string url = GetUrl(EndpointsConfiguration.Accounts.Authenticate);
|
||||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
|
HttpRequest request = new HttpRequest(HttpMethodType.Post, url)
|
||||||
@@ -39,10 +40,13 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
|
|||||||
.ExecuteAction();
|
.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 url = GetUrl(EndpointsConfiguration.Accounts.AuthenticateRefresh);
|
||||||
|
string? token = await tokensService.GetRefreshToken();
|
||||||
|
|
||||||
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
HttpRequest request = new HttpRequest(HttpMethodType.Post, url);
|
||||||
|
request.Headers.Add("Authorization", $"Bearer {token}");
|
||||||
|
|
||||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
response.RegisterActionFor2XXSuccess(successAction)
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
@@ -51,6 +55,31 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
|
|||||||
.ExecuteAction();
|
.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
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ namespace WatchIt.Website.Services.WebAPI.Accounts;
|
|||||||
|
|
||||||
public interface IAccountsWebAPIService
|
public interface IAccountsWebAPIService
|
||||||
{
|
{
|
||||||
Task Register(RegisterRequest data, Action<RegisterResponse> createdAction, Action<IDictionary<string, string[]>> badRequestAction);
|
Task Register(RegisterRequest data, Action<RegisterResponse>? createdAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null);
|
||||||
Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse> successAction, Action<IDictionary<string, string[]>> badRequestAction, Action unauthorizedAction);
|
Task Authenticate(AuthenticateRequest data, Action<AuthenticateResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null);
|
||||||
Task AuthenticateRefresh(Action<AuthenticateResponse> successAction, Action unauthorizedAction, Action forbiddenAction);
|
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;
|
||||||
using WatchIt.Website.Services.Utility.Configuration.Model;
|
using WatchIt.Website.Services.Utility.Configuration.Model;
|
||||||
|
using WatchIt.Website.Services.Utility.Tokens;
|
||||||
|
|
||||||
namespace WatchIt.Website.Services.WebAPI.Common;
|
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
|
#region FIELDS
|
||||||
|
|
||||||
protected Endpoints EndpointsConfiguration => configurationService.Data.Endpoints;
|
protected Endpoints EndpointsConfiguration => _configurationService.Data.Endpoints;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
protected BaseWebAPIService(IConfigurationService configurationService)
|
||||||
|
{
|
||||||
|
_configurationService = configurationService;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<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.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.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>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace WatchIt.Website.Services.WebAPI.Media;
|
|||||||
|
|
||||||
public interface IMediaWebAPIService
|
public interface IMediaWebAPIService
|
||||||
{
|
{
|
||||||
Task GetGenres(long mediaId, Action<IEnumerable<GenreResponse>> successAction, Action notFoundAction);
|
Task GetGenres(long mediaId, Action<IEnumerable<GenreResponse>>? successAction = null, Action? notFoundAction = null);
|
||||||
Task PostGenre(long mediaId, long genreId, Action successAction, Action unauthorizedAction, Action forbiddenAction, Action notFoundAction);
|
Task PostGenre(long mediaId, long genreId, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null, Action? notFoundAction = null);
|
||||||
Task GetPhotoRandomBackground(Action<MediaPhotoResponse> successAction, Action notFoundAction);
|
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>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</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>
|
</Project>
|
||||||
|
|||||||
@@ -1,14 +1,26 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<base href="/"/>
|
<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"/>
|
<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"/>
|
<HeadOutlet @rendermode="InteractiveServer"/>
|
||||||
</head>
|
</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
|
@inherits LayoutComponentBase
|
||||||
@using System.Text
|
|
||||||
@using WatchIt.Common.Model.Media
|
|
||||||
@using WatchIt.Website.Services.WebAPI.Media
|
|
||||||
@inherits LayoutComponentBase
|
|
||||||
|
|
||||||
@if (loaded)
|
@if (_loaded)
|
||||||
{
|
{
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="row align-items-center m-2 rounded-3 header panel">
|
<div class="row align-items-center m-1 my-2 rounded-3 header panel panel-header z-3">
|
||||||
<div class="col-sm-4">
|
<div class="col-2">
|
||||||
<a class="logo" href="/">
|
<a class="logo" href="/">
|
||||||
WatchIt
|
WatchIt
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4">
|
<div class="col">
|
||||||
<p>Menu</p>
|
<p>Menu</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4">
|
<div class="col-auto">
|
||||||
<div class="d-flex flex-row-reverse">
|
<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
|
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>
|
||||||
</div>
|
</div>
|
||||||
<div class="row body-content">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col z-0">
|
||||||
@Body
|
@Body
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,11 +49,16 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background: url('@background') no-repeat center center fixed;
|
background: url('@_background') no-repeat center center fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo, .main-button {
|
.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>
|
</style>
|
||||||
}
|
}
|
||||||
@@ -55,6 +71,10 @@
|
|||||||
#region SERVICES
|
#region SERVICES
|
||||||
|
|
||||||
[Inject] public ILogger<MainLayout> Logger { get; set; } = default!;
|
[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 IMediaWebAPIService MediaWebAPIService { get; set; } = default!;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -63,12 +83,15 @@
|
|||||||
|
|
||||||
#region FIELDS
|
#region FIELDS
|
||||||
|
|
||||||
private bool loaded = false;
|
private bool _loaded = false;
|
||||||
|
|
||||||
private string background = "assets/background_temp.jpg";
|
private string _background = "assets/background_temp.jpg";
|
||||||
private string firstGradientColor = "#c6721c";
|
private string _firstGradientColor = "#c6721c";
|
||||||
private string secondGradientColor = "#85200c";
|
private string _secondGradientColor = "#85200c";
|
||||||
private bool signedIn = false;
|
|
||||||
|
private User? _user = null;
|
||||||
|
private string _userProfilePicture = "assets/user_placeholder.png";
|
||||||
|
private bool _userMenuIsActive = false;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -76,7 +99,29 @@
|
|||||||
|
|
||||||
#region METHODS
|
#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) =>
|
Action<MediaPhotoResponse> backgroundSuccess = (data) =>
|
||||||
{
|
{
|
||||||
@@ -86,13 +131,33 @@
|
|||||||
string secondColor = BitConverter.ToString(data.Background.SecondGradientColor)
|
string secondColor = BitConverter.ToString(data.Background.SecondGradientColor)
|
||||||
.Replace("-", string.Empty);
|
.Replace("-", string.Empty);
|
||||||
|
|
||||||
background = $"data:{data.MimeType};base64,{imageBase64}";
|
_background = $"data:{data.MimeType};base64,{imageBase64}";
|
||||||
firstGradientColor = $"#{firstColor}";
|
_firstGradientColor = $"#{firstColor}";
|
||||||
secondGradientColor = $"#{secondColor}";
|
_secondGradientColor = $"#{secondColor}";
|
||||||
};
|
};
|
||||||
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess, null);
|
await MediaWebAPIService.GetPhotoRandomBackground(backgroundSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
loaded = true;
|
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
|
#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"
|
@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
|
@layout EmptyLayout
|
||||||
|
|
||||||
<PageTitle>WatchIt - @(_authType == AuthType.SignIn ? "Sign in" : "Sign up")</PageTitle>
|
<PageTitle>WatchIt - @(_authType == AuthType.SignIn ? "Sign in" : "Sign up")</PageTitle>
|
||||||
@@ -6,24 +13,101 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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 rounded-3">
|
<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="/">
|
<a class="logo" href="/">
|
||||||
WatchIt
|
WatchIt
|
||||||
</a>
|
</a>
|
||||||
|
<div>
|
||||||
@if (_authType == AuthType.SignIn)
|
@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>
|
</div>
|
||||||
</div>
|
@if (_errors is not null)
|
||||||
|
{
|
||||||
|
<div class="text-danger">
|
||||||
|
@foreach (string error in _errors)
|
||||||
|
{
|
||||||
|
@error
|
||||||
|
<br/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="radio" checked="@(() => _authType == AuthType.SignIn)" name="auth" @onchange="@(() => _authType = AuthType.SignIn)" />
|
||||||
|
Sign in
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" checked="@(() => _authType == AuthType.SignUp)" name="auth" @onchange="@(() => _authType = AuthType.SignUp)" />
|
||||||
|
Sign up
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
body {
|
||||||
background: url('@(_background)') no-repeat center center fixed;
|
background: url('@(_background)') no-repeat center center fixed;
|
||||||
}
|
}
|
||||||
@@ -31,13 +115,27 @@
|
|||||||
.logo {
|
.logo {
|
||||||
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
background-image: linear-gradient(45deg, @_firstGradientColor, @_secondGradientColor);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[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!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region ENUMS
|
#region ENUMS
|
||||||
|
|
||||||
@@ -53,20 +151,99 @@
|
|||||||
|
|
||||||
#region FIELDS
|
#region FIELDS
|
||||||
|
|
||||||
|
private bool _loaded = false;
|
||||||
|
|
||||||
private AuthType _authType = AuthType.SignIn;
|
private AuthType _authType = AuthType.SignIn;
|
||||||
private string _background = "assets/background_temp.jpg";
|
private string _background = "assets/background_temp.jpg";
|
||||||
private string _firstGradientColor = "#c6721c";
|
private string _firstGradientColor = "#c6721c";
|
||||||
private string _secondGradientColor = "#85200c";
|
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
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region METHODS
|
#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
|
#endregion
|
||||||
|
|||||||
@@ -5,46 +5,50 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h1>Hello, world!</h1>
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
Welcome to your new app.
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
|
<h2>Hello, world!</h2>
|
||||||
<h1>Hello, world!</h1>
|
<p>Welcome to your new app.</p>
|
||||||
|
<h2>Hello, world!</h2>
|
||||||
Welcome to your new app.
|
<p>Welcome to your new app.</p>
|
||||||
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
<h1>Hello, world!</h1>
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
Welcome to your new app.
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
<h1>Hello, world!</h1>
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
Welcome to your new app.
|
<h2>Hello, world!</h2>
|
||||||
<h1>Hello, world!</h1>
|
<p>Welcome to your new app.</p>
|
||||||
|
<h2>Hello, world!</h2>
|
||||||
Welcome to your new app.
|
<p>Welcome to your new app.</p>
|
||||||
<h1>Hello, world!</h1>
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
Welcome to your new app.
|
<h2>Hello, world!</h2>
|
||||||
<h1>Hello, world!</h1>
|
<p>Welcome to your new app.</p>
|
||||||
|
<h2>Hello, world!</h2>
|
||||||
Welcome to your new app.
|
<p>Welcome to your new app.</p>
|
||||||
<h1>Hello, world!</h1>
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
Welcome to your new app.
|
<h2>Hello, world!</h2>
|
||||||
<h1>Hello, world!</h1>
|
<p>Welcome to your new app.</p>
|
||||||
|
<h2>Hello, world!</h2>
|
||||||
Welcome to your new app.
|
<p>Welcome to your new app.</p>
|
||||||
<h1>Hello, world!</h1>
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
Welcome to your new app.
|
<h2>Hello, world!</h2>
|
||||||
|
<p>Welcome to your new app.</p>
|
||||||
|
<h2>Hello, world!</h2>
|
||||||
<h1>Hello, world!</h1>
|
<p>Welcome to your new app.</p>
|
||||||
|
<h2>Hello, world!</h2>
|
||||||
Welcome to your new app.
|
<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>
|
</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.Common.Services.HttpClient;
|
||||||
|
using WatchIt.Website.Services.Utility.Authentication;
|
||||||
using WatchIt.Website.Services.Utility.Configuration;
|
using WatchIt.Website.Services.Utility.Configuration;
|
||||||
|
using WatchIt.Website.Services.Utility.Tokens;
|
||||||
using WatchIt.Website.Services.WebAPI.Accounts;
|
using WatchIt.Website.Services.WebAPI.Accounts;
|
||||||
using WatchIt.Website.Services.WebAPI.Media;
|
using WatchIt.Website.Services.WebAPI.Media;
|
||||||
|
using WatchIt.Website.Services.WebAPI.Movies;
|
||||||
|
|
||||||
namespace WatchIt.Website;
|
namespace WatchIt.Website;
|
||||||
|
|
||||||
@@ -13,6 +19,7 @@ public static class Program
|
|||||||
{
|
{
|
||||||
WebApplication app = WebApplication.CreateBuilder(args)
|
WebApplication app = WebApplication.CreateBuilder(args)
|
||||||
.SetupServices()
|
.SetupServices()
|
||||||
|
.SetupAuthentication()
|
||||||
.SetupApplication()
|
.SetupApplication()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@@ -43,15 +50,26 @@ public static class Program
|
|||||||
|
|
||||||
private static WebApplicationBuilder SetupServices(this WebApplicationBuilder builder)
|
private static WebApplicationBuilder SetupServices(this WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddSingleton<HttpClient>();
|
||||||
|
|
||||||
// Utility
|
// Utility
|
||||||
builder.Services.AddSingleton<IHttpClientService, HttpClientService>();
|
builder.Services.AddSingleton<IHttpClientService, HttpClientService>();
|
||||||
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
|
builder.Services.AddSingleton<IConfigurationService, ConfigurationService>();
|
||||||
|
builder.Services.AddScoped<ITokensService, TokensService>();
|
||||||
|
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
|
||||||
|
|
||||||
// WebAPI
|
// WebAPI
|
||||||
builder.Services.AddSingleton<IAccountsWebAPIService, AccountsWebAPIService>();
|
builder.Services.AddScoped<IAccountsWebAPIService, AccountsWebAPIService>();
|
||||||
builder.Services.AddSingleton<IMediaWebAPIService, MediaWebAPIService>();
|
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;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.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.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.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.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.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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -27,6 +31,8 @@
|
|||||||
<_ContentIncludedByDefault Remove="Components\Pages\Error.razor" />
|
<_ContentIncludedByDefault Remove="Components\Pages\Error.razor" />
|
||||||
<_ContentIncludedByDefault Remove="Components\Pages\Home.razor" />
|
<_ContentIncludedByDefault Remove="Components\Pages\Home.razor" />
|
||||||
<_ContentIncludedByDefault Remove="Components\Pages\Weather.razor" />
|
<_ContentIncludedByDefault Remove="Components\Pages\Weather.razor" />
|
||||||
|
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css" />
|
||||||
|
<_ContentIncludedByDefault Remove="wwwroot\bootstrap\bootstrap.min.css.map" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -34,9 +40,4 @@
|
|||||||
<AdditionalFiles Include="Pages\Home.razor" />
|
<AdditionalFiles Include="Pages\Home.razor" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Components\" />
|
|
||||||
<Folder Include="wwwroot\assets\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -8,3 +8,10 @@
|
|||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using WatchIt.Website
|
@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": "*",
|
"AllowedHosts": "*",
|
||||||
|
"StorageKeys": {
|
||||||
|
"AccessToken": "access_token",
|
||||||
|
"RefreshToken": "refresh_token"
|
||||||
|
},
|
||||||
"Endpoints": {
|
"Endpoints": {
|
||||||
"Base": "https://localhost:7160",
|
"Base": "https://localhost:7160",
|
||||||
"Accounts": {
|
"Accounts": {
|
||||||
"Base": "/accounts",
|
"Base": "/accounts",
|
||||||
"Register": "/register",
|
"Register": "/register",
|
||||||
"Authenticate": "/authenticate",
|
"Authenticate": "/authenticate",
|
||||||
"AuthenticateRefresh": "/authenticate-refresh"
|
"AuthenticateRefresh": "/authenticate-refresh",
|
||||||
|
"Logout": "/logout",
|
||||||
|
"GetProfilePicture": "/{0}/profile-picture"
|
||||||
},
|
},
|
||||||
"Genres": {
|
"Genres": {
|
||||||
"Base": "/genres",
|
"Base": "/genres",
|
||||||
@@ -28,10 +34,7 @@
|
|||||||
"Get": "/{0}",
|
"Get": "/{0}",
|
||||||
"Post": "",
|
"Post": "",
|
||||||
"Put": "/{0}",
|
"Put": "/{0}",
|
||||||
"Delete": "/{0}",
|
"Delete": "/{0}"
|
||||||
"GetGenres": "/{0}/genres",
|
|
||||||
"PostGenre": "{0}/genres/{1}",
|
|
||||||
"DeleteGenre": "{0}/genres/{1}"
|
|
||||||
},
|
},
|
||||||
"Media": {
|
"Media": {
|
||||||
"Base": "/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 {
|
body, html {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
color: lightgray;
|
||||||
|
font-family: "PT Sans";
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
font-family: Belanosima;
|
font-family: "Belanosima";
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel-header {
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
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);
|
backdrop-filter: blur(25px);
|
||||||
z-index: 1000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-button {
|
.main-button {
|
||||||
@@ -78,3 +76,8 @@ body, html {
|
|||||||
.main-button:hover::before {
|
.main-button:hover::before {
|
||||||
-webkit-mask:none;
|
-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.
14
WatchIt.sln
14
WatchIt.sln
@@ -76,6 +76,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.We
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.WebAPI.Common", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Common\WatchIt.Website.Services.WebAPI.Common.csproj", "{2D62ED42-489E-4888-9479-E5A50A0E7D70}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.WebAPI.Common", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Common\WatchIt.Website.Services.WebAPI.Common.csproj", "{2D62ED42-489E-4888-9479-E5A50A0E7D70}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.Utility.Tokens", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Tokens\WatchIt.Website.Services.Utility.Tokens.csproj", "{77FDAFDD-E97E-4059-A935-B563B6B0D555}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.Utility.Authentication", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.Utility\WatchIt.Website.Services.Utility.Authentication\WatchIt.Website.Services.Utility.Authentication.csproj", "{8720AECA-7084-429A-BA15-49B6622C1A32}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -116,6 +120,8 @@ Global
|
|||||||
{3156AD7B-D6EC-4EB6-AEE8-4FBAF14C18E4} = {CEC468DB-CC49-47D3-9E3E-1CC9530C3CE7}
|
{3156AD7B-D6EC-4EB6-AEE8-4FBAF14C18E4} = {CEC468DB-CC49-47D3-9E3E-1CC9530C3CE7}
|
||||||
{1D64B7B5-650D-4AF3-AC33-A8D1F0999906} = {46E3711F-18BD-4004-AF53-EA4D8643D92F}
|
{1D64B7B5-650D-4AF3-AC33-A8D1F0999906} = {46E3711F-18BD-4004-AF53-EA4D8643D92F}
|
||||||
{2D62ED42-489E-4888-9479-E5A50A0E7D70} = {46E3711F-18BD-4004-AF53-EA4D8643D92F}
|
{2D62ED42-489E-4888-9479-E5A50A0E7D70} = {46E3711F-18BD-4004-AF53-EA4D8643D92F}
|
||||||
|
{77FDAFDD-E97E-4059-A935-B563B6B0D555} = {130BC8F5-82CE-4EDF-AECB-21594DD41849}
|
||||||
|
{8720AECA-7084-429A-BA15-49B6622C1A32} = {130BC8F5-82CE-4EDF-AECB-21594DD41849}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{23383776-1F27-4B5D-8C7C-57BFF75FA473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{23383776-1F27-4B5D-8C7C-57BFF75FA473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
@@ -222,5 +228,13 @@ Global
|
|||||||
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Release|Any CPU.Build.0 = Release|Any CPU
|
{2D62ED42-489E-4888-9479-E5A50A0E7D70}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{77FDAFDD-E97E-4059-A935-B563B6B0D555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{77FDAFDD-E97E-4059-A935-B563B6B0D555}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{77FDAFDD-E97E-4059-A935-B563B6B0D555}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{77FDAFDD-E97E-4059-A935-B563B6B0D555}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8720AECA-7084-429A-BA15-49B6622C1A32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8720AECA-7084-429A-BA15-49B6622C1A32}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8720AECA-7084-429A-BA15-49B6622C1A32}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8720AECA-7084-429A-BA15-49B6622C1A32}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
Reference in New Issue
Block a user