Merge pull request #151 from mateuszskoczek/features/user_edit_page
Features/user edit page
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace WatchIt.Common.Model.Accounts;
|
||||||
|
|
||||||
|
public class AccountEmailRequest
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("new_email")]
|
||||||
|
public string NewEmail { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("password")]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public void UpdateAccount(Database.Model.Account.Account account)
|
||||||
|
{
|
||||||
|
account.Email = NewEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace WatchIt.Common.Model.Accounts;
|
||||||
|
|
||||||
|
public class AccountPasswordRequest
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("old_password")]
|
||||||
|
public string OldPassword { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("new_password")]
|
||||||
|
public string NewPassword { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("new_password_confirmation")]
|
||||||
|
public string NewPasswordConfirmation { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace WatchIt.Common.Model.Accounts;
|
||||||
|
|
||||||
|
public class AccountProfileBackgroundRequest
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public required Guid Id { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
public AccountProfileBackgroundRequest(Guid id)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -2,10 +2,13 @@ using System.Text.Json.Serialization;
|
|||||||
|
|
||||||
namespace WatchIt.Common.Model.Accounts;
|
namespace WatchIt.Common.Model.Accounts;
|
||||||
|
|
||||||
public class AccountRequest : Account
|
public class AccountProfileInfoRequest
|
||||||
{
|
{
|
||||||
#region PROPERTIES
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("description")]
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("gender_id")]
|
[JsonPropertyName("gender_id")]
|
||||||
public short? GenderId { get; set; }
|
public short? GenderId { get; set; }
|
||||||
|
|
||||||
@@ -13,12 +16,24 @@ public class AccountRequest : Account
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public AccountProfileInfoRequest() { }
|
||||||
|
|
||||||
|
public AccountProfileInfoRequest(AccountResponse accountResponse)
|
||||||
|
{
|
||||||
|
Description = accountResponse.Description;
|
||||||
|
GenderId = accountResponse.Gender?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PUBLIC METHODS
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
public void UpdateAccount(Database.Model.Account.Account account)
|
public void UpdateAccount(Database.Model.Account.Account account)
|
||||||
{
|
{
|
||||||
account.Username = Username;
|
|
||||||
account.Email = Email;
|
|
||||||
account.Description = Description;
|
account.Description = Description;
|
||||||
account.GenderId = GenderId;
|
account.GenderId = GenderId;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace WatchIt.Common.Model.Accounts;
|
||||||
|
|
||||||
|
public class AccountProfilePictureRequest : AccountProfilePicture
|
||||||
|
{
|
||||||
|
#region CONSTRUCTORS
|
||||||
|
|
||||||
|
public AccountProfilePictureRequest() {}
|
||||||
|
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
public AccountProfilePictureRequest(Picture image)
|
||||||
|
{
|
||||||
|
Image = image.Image;
|
||||||
|
MimeType = image.MimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
public Database.Model.Account.AccountProfilePicture CreateMediaPosterImage() => new Database.Model.Account.AccountProfilePicture
|
||||||
|
{
|
||||||
|
Image = Image,
|
||||||
|
MimeType = MimeType,
|
||||||
|
};
|
||||||
|
|
||||||
|
public void UpdateMediaPosterImage(Database.Model.Account.AccountProfilePicture item)
|
||||||
|
{
|
||||||
|
item.Image = Image;
|
||||||
|
item.MimeType = MimeType;
|
||||||
|
item.UploadDate = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace WatchIt.Common.Model.Accounts;
|
||||||
|
|
||||||
|
public class AccountUsernameRequest
|
||||||
|
{
|
||||||
|
#region PROPERTIES
|
||||||
|
|
||||||
|
[JsonPropertyName("new_username")]
|
||||||
|
public string NewUsername { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("password")]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public void UpdateAccount(Database.Model.Account.Account account)
|
||||||
|
{
|
||||||
|
account.Username = NewUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -15,9 +15,9 @@ public class Account
|
|||||||
public short? GenderId { get; set; }
|
public short? GenderId { get; set; }
|
||||||
public Guid? ProfilePictureId { get; set; }
|
public Guid? ProfilePictureId { get; set; }
|
||||||
public Guid? BackgroundPictureId { get; set; }
|
public Guid? BackgroundPictureId { get; set; }
|
||||||
public required byte[] Password { get; set; }
|
public byte[] Password { get; set; }
|
||||||
public required string LeftSalt { get; set; }
|
public string LeftSalt { get; set; }
|
||||||
public required string RightSalt { get; set; }
|
public string RightSalt { get; set; }
|
||||||
public bool IsAdmin { get; set; } = false;
|
public bool IsAdmin { get; set; } = false;
|
||||||
public DateTime CreationDate { get; set; }
|
public DateTime CreationDate { get; set; }
|
||||||
public DateTime LastActive { get; set; }
|
public DateTime LastActive { get; set; }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using WatchIt.Common.Model.Accounts;
|
using WatchIt.Common.Model.Accounts;
|
||||||
using WatchIt.Common.Model.Movies;
|
using WatchIt.Common.Model.Movies;
|
||||||
using WatchIt.Common.Model.Persons;
|
using WatchIt.Common.Model.Persons;
|
||||||
|
using WatchIt.Common.Model.Photos;
|
||||||
using WatchIt.Common.Model.Series;
|
using WatchIt.Common.Model.Series;
|
||||||
using WatchIt.WebAPI.Services.Controllers.Accounts;
|
using WatchIt.WebAPI.Services.Controllers.Accounts;
|
||||||
|
|
||||||
@@ -14,6 +15,8 @@ namespace WatchIt.WebAPI.Controllers;
|
|||||||
[Route("accounts")]
|
[Route("accounts")]
|
||||||
public class AccountsController(IAccountsControllerService accountsControllerService) : ControllerBase
|
public class AccountsController(IAccountsControllerService accountsControllerService) : ControllerBase
|
||||||
{
|
{
|
||||||
|
#region Basic
|
||||||
|
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ProducesResponseType(typeof(RegisterResponse), StatusCodes.Status201Created)]
|
[ProducesResponseType(typeof(RegisterResponse), StatusCodes.Status201Created)]
|
||||||
@@ -27,7 +30,7 @@ public class AccountsController(IAccountsControllerService accountsControllerSer
|
|||||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
public async Task<ActionResult> Authenticate([FromBody]AuthenticateRequest body) => await accountsControllerService.Authenticate(body);
|
public async Task<ActionResult> Authenticate([FromBody]AuthenticateRequest body) => await accountsControllerService.Authenticate(body);
|
||||||
|
|
||||||
[HttpPost("authenticate-refresh")]
|
[HttpPost("authenticate_refresh")]
|
||||||
[Authorize(AuthenticationSchemes = "refresh")]
|
[Authorize(AuthenticationSchemes = "refresh")]
|
||||||
[ProducesResponseType(typeof(AuthenticateResponse), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(AuthenticateResponse), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
@@ -39,25 +42,93 @@ public class AccountsController(IAccountsControllerService accountsControllerSer
|
|||||||
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
||||||
public async Task<ActionResult> Logout() => await accountsControllerService.Logout();
|
public async Task<ActionResult> Logout() => await accountsControllerService.Logout();
|
||||||
|
|
||||||
[HttpGet("{id}/profile-picture")]
|
#endregion
|
||||||
|
|
||||||
|
#region Profile picture
|
||||||
|
|
||||||
|
[HttpGet("{id}/profile_picture")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ProducesResponseType(typeof(AccountProfilePictureResponse), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(AccountProfilePictureResponse), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<ActionResult> GetAccountProfilePicture([FromRoute(Name = "id")]long id) => await accountsControllerService.GetAccountProfilePicture(id);
|
public async Task<ActionResult> GetAccountProfilePicture([FromRoute(Name = "id")]long id) => await accountsControllerService.GetAccountProfilePicture(id);
|
||||||
|
|
||||||
|
[HttpPut("profile_picture")]
|
||||||
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
|
[ProducesResponseType(typeof(AccountProfilePictureResponse), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult> PutAccountProfilePicture([FromBody]AccountProfilePictureRequest body) => await accountsControllerService.PutAccountProfilePicture(body);
|
||||||
|
|
||||||
|
[HttpDelete("profile_picture")]
|
||||||
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
|
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult> DeleteAccountProfilePicture() => await accountsControllerService.DeleteAccountProfilePicture();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Profile background
|
||||||
|
|
||||||
|
[HttpGet("{id}/profile_background")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ProducesResponseType(typeof(PhotoResponse), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult> GetAccountProfileBackground([FromRoute(Name = "id")]long id) => await accountsControllerService.GetAccountProfileBackground(id);
|
||||||
|
|
||||||
|
[HttpPut("profile_background")]
|
||||||
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
|
[ProducesResponseType(typeof(PhotoResponse), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult> PutAccountProfileBackground([FromBody]AccountProfileBackgroundRequest body) => await accountsControllerService.PutAccountProfileBackground(body);
|
||||||
|
|
||||||
|
[HttpDelete("profile_background")]
|
||||||
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
|
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult> DeleteAccountProfileBackground() => await accountsControllerService.DeleteAccountProfileBackground();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Info
|
||||||
|
|
||||||
[HttpGet("{id}/info")]
|
[HttpGet("{id}/info")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<ActionResult> GetAccountInfo([FromRoute]long id) => await accountsControllerService.GetAccountInfo(id);
|
public async Task<ActionResult> GetAccountInfo([FromRoute]long id) => await accountsControllerService.GetAccountInfo(id);
|
||||||
|
|
||||||
[HttpPut("info")]
|
[HttpPut("profile_info")]
|
||||||
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
[ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<ActionResult> PutAccountInfo([FromBody]AccountRequest data) => await accountsControllerService.PutAccountInfo(data);
|
public async Task<ActionResult> PutAccountProfileInfo([FromBody]AccountProfileInfoRequest data) => await accountsControllerService.PutAccountProfileInfo(data);
|
||||||
|
|
||||||
|
[HttpPatch("username")]
|
||||||
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult> PatchAccountUsername([FromBody]AccountUsernameRequest data) => await accountsControllerService.PatchAccountUsername(data);
|
||||||
|
|
||||||
|
[HttpPatch("email")]
|
||||||
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult> PatchAccountEmail([FromBody]AccountEmailRequest data) => await accountsControllerService.PatchAccountEmail(data);
|
||||||
|
|
||||||
|
[HttpPatch("password")]
|
||||||
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
public async Task<ActionResult> PatchAccountPassword([FromBody]AccountPasswordRequest data) => await accountsControllerService.PatchAccountPassword(data);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
[HttpGet("{id}/movies")]
|
[HttpGet("{id}/movies")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using WatchIt.Common.Model.Accounts;
|
|||||||
using WatchIt.Common.Model.Media;
|
using WatchIt.Common.Model.Media;
|
||||||
using WatchIt.Common.Model.Movies;
|
using WatchIt.Common.Model.Movies;
|
||||||
using WatchIt.Common.Model.Persons;
|
using WatchIt.Common.Model.Persons;
|
||||||
|
using WatchIt.Common.Model.Photos;
|
||||||
using WatchIt.Common.Model.Series;
|
using WatchIt.Common.Model.Series;
|
||||||
using WatchIt.Database;
|
using WatchIt.Database;
|
||||||
using WatchIt.Database.Model.Account;
|
using WatchIt.Database.Model.Account;
|
||||||
@@ -32,20 +33,17 @@ public class AccountsControllerService(
|
|||||||
{
|
{
|
||||||
#region PUBLIC METHODS
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
#region Basic
|
||||||
|
|
||||||
public async Task<RequestResult> Register(RegisterRequest data)
|
public async Task<RequestResult> Register(RegisterRequest data)
|
||||||
{
|
{
|
||||||
string leftSalt = StringExtensions.CreateRandom(20);
|
|
||||||
string rightSalt = StringExtensions.CreateRandom(20);
|
|
||||||
byte[] hash = ComputeHash(data.Password, leftSalt, rightSalt);
|
|
||||||
|
|
||||||
Account account = new Account
|
Account account = new Account
|
||||||
{
|
{
|
||||||
Username = data.Username,
|
Username = data.Username,
|
||||||
Email = data.Email,
|
Email = data.Email,
|
||||||
Password = hash,
|
|
||||||
LeftSalt = leftSalt,
|
|
||||||
RightSalt = rightSalt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SetPassword(account, data.Password);
|
||||||
await database.Accounts.AddAsync(account);
|
await database.Accounts.AddAsync(account);
|
||||||
await database.SaveChangesAsync();
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
@@ -125,6 +123,10 @@ public class AccountsControllerService(
|
|||||||
return RequestResult.NoContent();
|
return RequestResult.NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Profile picture
|
||||||
|
|
||||||
public async Task<RequestResult> GetAccountProfilePicture(long id)
|
public async Task<RequestResult> GetAccountProfilePicture(long id)
|
||||||
{
|
{
|
||||||
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
|
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
@@ -143,6 +145,99 @@ public class AccountsControllerService(
|
|||||||
return RequestResult.Ok(picture);
|
return RequestResult.Ok(picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult> PutAccountProfilePicture(AccountProfilePictureRequest data)
|
||||||
|
{
|
||||||
|
Account account = await database.Accounts.FirstAsync(x => x.Id == userService.GetUserId());
|
||||||
|
Database.Model.Account.AccountProfilePicture? picture = account.ProfilePicture;
|
||||||
|
|
||||||
|
if (picture is null)
|
||||||
|
{
|
||||||
|
picture = data.CreateMediaPosterImage();
|
||||||
|
await database.AccountProfilePictures.AddAsync(picture);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
account.ProfilePictureId = picture.Id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data.UpdateMediaPosterImage(picture);
|
||||||
|
}
|
||||||
|
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
AccountProfilePictureResponse returnData = new AccountProfilePictureResponse(picture);
|
||||||
|
return RequestResult.Ok(returnData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult> DeleteAccountProfilePicture()
|
||||||
|
{
|
||||||
|
Account account = await database.Accounts.FirstAsync(x => x.Id == userService.GetUserId());
|
||||||
|
Database.Model.Account.AccountProfilePicture? picture = account.ProfilePicture;
|
||||||
|
|
||||||
|
if (picture is not null)
|
||||||
|
{
|
||||||
|
account.ProfilePictureId = null;
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
database.AccountProfilePictures.Attach(picture);
|
||||||
|
database.AccountProfilePictures.Remove(picture);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return RequestResult.NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Profile background
|
||||||
|
|
||||||
|
public async Task<RequestResult> GetAccountProfileBackground(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.BackgroundPicture is null)
|
||||||
|
{
|
||||||
|
return RequestResult.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
PhotoResponse response = new PhotoResponse(account.BackgroundPicture);
|
||||||
|
return RequestResult.Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult> PutAccountProfileBackground(AccountProfileBackgroundRequest data)
|
||||||
|
{
|
||||||
|
Account account = await database.Accounts.FirstAsync(x => x.Id == userService.GetUserId());
|
||||||
|
|
||||||
|
account.BackgroundPictureId = data.Id;
|
||||||
|
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
PhotoResponse returnData = new PhotoResponse(account.BackgroundPicture!);
|
||||||
|
return RequestResult.Ok(returnData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult> DeleteAccountProfileBackground()
|
||||||
|
{
|
||||||
|
Account account = await database.Accounts.FirstAsync(x => x.Id == userService.GetUserId());
|
||||||
|
|
||||||
|
if (account.BackgroundPicture is not null)
|
||||||
|
{
|
||||||
|
account.BackgroundPictureId = null;
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return RequestResult.NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Info
|
||||||
|
|
||||||
public async Task<RequestResult> GetAccountInfo(long id)
|
public async Task<RequestResult> GetAccountInfo(long id)
|
||||||
{
|
{
|
||||||
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
|
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
@@ -151,11 +246,11 @@ public class AccountsControllerService(
|
|||||||
return RequestResult.NotFound();
|
return RequestResult.NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountResponse response = new AccountResponse(account);
|
AccountResponse profileInfoResponse = new AccountResponse(account);
|
||||||
return RequestResult.Ok(response);
|
return RequestResult.Ok(profileInfoResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestResult> PutAccountInfo(AccountRequest data)
|
public async Task<RequestResult> PutAccountProfileInfo(AccountProfileInfoRequest data)
|
||||||
{
|
{
|
||||||
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == userService.GetUserId());
|
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == userService.GetUserId());
|
||||||
if (account is null)
|
if (account is null)
|
||||||
@@ -164,9 +259,58 @@ public class AccountsControllerService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data.UpdateAccount(account);
|
data.UpdateAccount(account);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
return RequestResult.Ok();
|
return RequestResult.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult> PatchAccountUsername(AccountUsernameRequest data)
|
||||||
|
{
|
||||||
|
Account account = await database.Accounts.FirstAsync(x => x.Id == userService.GetUserId());
|
||||||
|
|
||||||
|
if (!ComputeHash(data.Password, account.LeftSalt, account.RightSalt).SequenceEqual(account.Password))
|
||||||
|
{
|
||||||
|
return RequestResult.Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
data.UpdateAccount(account);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
return RequestResult.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult> PatchAccountEmail(AccountEmailRequest data)
|
||||||
|
{
|
||||||
|
Account account = await database.Accounts.FirstAsync(x => x.Id == userService.GetUserId());
|
||||||
|
|
||||||
|
if (!ComputeHash(data.Password, account.LeftSalt, account.RightSalt).SequenceEqual(account.Password))
|
||||||
|
{
|
||||||
|
return RequestResult.Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
data.UpdateAccount(account);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
return RequestResult.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestResult> PatchAccountPassword(AccountPasswordRequest data)
|
||||||
|
{
|
||||||
|
Account account = await database.Accounts.FirstAsync(x => x.Id == userService.GetUserId());
|
||||||
|
|
||||||
|
if (!ComputeHash(data.OldPassword, account.LeftSalt, account.RightSalt).SequenceEqual(account.Password))
|
||||||
|
{
|
||||||
|
return RequestResult.Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPassword(account, data.NewPassword);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
|
||||||
|
return RequestResult.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
public async Task<RequestResult> GetAccountRatedMovies(long id, MovieRatedQueryParameters query)
|
public async Task<RequestResult> GetAccountRatedMovies(long id, MovieRatedQueryParameters query)
|
||||||
{
|
{
|
||||||
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
|
Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
@@ -219,5 +363,16 @@ public class AccountsControllerService(
|
|||||||
|
|
||||||
protected byte[] ComputeHash(string password, string leftSalt, string rightSalt) => SHA512.HashData(Encoding.UTF8.GetBytes($"{leftSalt}{password}{rightSalt}"));
|
protected byte[] ComputeHash(string password, string leftSalt, string rightSalt) => SHA512.HashData(Encoding.UTF8.GetBytes($"{leftSalt}{password}{rightSalt}"));
|
||||||
|
|
||||||
|
private void SetPassword(Account account, string password)
|
||||||
|
{
|
||||||
|
string leftSalt = StringExtensions.CreateRandom(20);
|
||||||
|
string rightSalt = StringExtensions.CreateRandom(20);
|
||||||
|
byte[] hash = ComputeHash(password, leftSalt, rightSalt);
|
||||||
|
|
||||||
|
account.Password = hash;
|
||||||
|
account.LeftSalt = leftSalt;
|
||||||
|
account.RightSalt = rightSalt;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -14,8 +14,16 @@ public interface IAccountsControllerService
|
|||||||
Task<RequestResult> AuthenticateRefresh();
|
Task<RequestResult> AuthenticateRefresh();
|
||||||
Task<RequestResult> Logout();
|
Task<RequestResult> Logout();
|
||||||
Task<RequestResult> GetAccountProfilePicture(long id);
|
Task<RequestResult> GetAccountProfilePicture(long id);
|
||||||
|
Task<RequestResult> PutAccountProfilePicture(AccountProfilePictureRequest data);
|
||||||
|
Task<RequestResult> DeleteAccountProfilePicture();
|
||||||
|
Task<RequestResult> GetAccountProfileBackground(long id);
|
||||||
|
Task<RequestResult> PutAccountProfileBackground(AccountProfileBackgroundRequest data);
|
||||||
|
Task<RequestResult> DeleteAccountProfileBackground();
|
||||||
Task<RequestResult> GetAccountInfo(long id);
|
Task<RequestResult> GetAccountInfo(long id);
|
||||||
Task<RequestResult> PutAccountInfo(AccountRequest data);
|
Task<RequestResult> PutAccountProfileInfo(AccountProfileInfoRequest data);
|
||||||
|
Task<RequestResult> PatchAccountUsername(AccountUsernameRequest data);
|
||||||
|
Task<RequestResult> PatchAccountEmail(AccountEmailRequest data);
|
||||||
|
Task<RequestResult> PatchAccountPassword(AccountPasswordRequest data);
|
||||||
Task<RequestResult> GetAccountRatedMovies(long id, MovieRatedQueryParameters query);
|
Task<RequestResult> GetAccountRatedMovies(long id, MovieRatedQueryParameters query);
|
||||||
Task<RequestResult> GetAccountRatedSeries(long id, SeriesRatedQueryParameters query);
|
Task<RequestResult> GetAccountRatedSeries(long id, SeriesRatedQueryParameters query);
|
||||||
Task<RequestResult> GetAccountRatedPersons(long id, PersonRatedQueryParameters query);
|
Task<RequestResult> GetAccountRatedPersons(long id, PersonRatedQueryParameters query);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Database;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Validators.Accounts;
|
||||||
|
|
||||||
|
public class AccountEmailRequestValidator : AbstractValidator<AccountEmailRequest>
|
||||||
|
{
|
||||||
|
public AccountEmailRequestValidator(DatabaseContext database)
|
||||||
|
{
|
||||||
|
RuleFor(x => x.NewEmail).EmailAddress()
|
||||||
|
.CannotBeIn(database.Accounts, x => x.Email)
|
||||||
|
.WithMessage("Email was already used");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Database;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Validators.Accounts;
|
||||||
|
|
||||||
|
public class AccountPasswordRequestValidator : AbstractValidator<AccountPasswordRequest>
|
||||||
|
{
|
||||||
|
public AccountPasswordRequestValidator(DatabaseContext database)
|
||||||
|
{
|
||||||
|
RuleFor(x => x.NewPassword).MinimumLength(8)
|
||||||
|
.Must(x => x.Any(char.IsUpper)).WithMessage("Password must contain at least one uppercase letter.")
|
||||||
|
.Must(x => x.Any(char.IsLower)).WithMessage("Password must contain at least one lowercase letter.")
|
||||||
|
.Must(x => x.Any(char.IsDigit)).WithMessage("Password must contain at least one digit.")
|
||||||
|
.Equal(x => x.NewPasswordConfirmation);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Database;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Validators.Accounts;
|
||||||
|
|
||||||
|
public class AccountProfileBackgroundRequestValidator : AbstractValidator<AccountProfileBackgroundRequest>
|
||||||
|
{
|
||||||
|
public AccountProfileBackgroundRequestValidator(DatabaseContext database)
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Id).MustBeIn(database.MediaPhotoImages.Where(x => x.MediaPhotoImageBackground != null), x => x.Id)
|
||||||
|
.WithMessage("Image has to be background");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,10 @@ using WatchIt.Database;
|
|||||||
|
|
||||||
namespace WatchIt.WebAPI.Validators.Accounts;
|
namespace WatchIt.WebAPI.Validators.Accounts;
|
||||||
|
|
||||||
public class AccountRequestValidator : AbstractValidator<AccountRequest>
|
public class AccountProfileInfoRequestValidator : AbstractValidator<AccountProfileInfoRequest>
|
||||||
{
|
{
|
||||||
public AccountRequestValidator(DatabaseContext database)
|
public AccountProfileInfoRequestValidator(DatabaseContext database)
|
||||||
{
|
{
|
||||||
RuleFor(x => x.Username).NotEmpty()
|
|
||||||
.MaximumLength(50);
|
|
||||||
RuleFor(x => x.Email).EmailAddress()
|
|
||||||
.MaximumLength(320);
|
|
||||||
RuleFor(x => x.Description).MaximumLength(1000);
|
RuleFor(x => x.Description).MaximumLength(1000);
|
||||||
When(x => x.GenderId.HasValue, () =>
|
When(x => x.GenderId.HasValue, () =>
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Validators.Accounts;
|
||||||
|
|
||||||
|
public class AccountProfilePictureRequestValidator : AbstractValidator<AccountProfilePictureRequest>
|
||||||
|
{
|
||||||
|
public AccountProfilePictureRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Image).NotEmpty();
|
||||||
|
RuleFor(x => x.MimeType).Matches(@"\w+/.+").WithMessage("Incorrect mimetype");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Database;
|
||||||
|
|
||||||
|
namespace WatchIt.WebAPI.Validators.Accounts;
|
||||||
|
|
||||||
|
public class AccountUsernameRequestValidator : AbstractValidator<AccountUsernameRequest>
|
||||||
|
{
|
||||||
|
public AccountUsernameRequestValidator(DatabaseContext database)
|
||||||
|
{
|
||||||
|
RuleFor(x => x.NewUsername).MinimumLength(5)
|
||||||
|
.MaximumLength(50)
|
||||||
|
.CannotBeIn(database.Accounts, x => x.Username)
|
||||||
|
.WithMessage("Username is already used");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using WatchIt.Common.Model.Accounts;
|
using WatchIt.Common.Model.Accounts;
|
||||||
using WatchIt.Common.Model.Movies;
|
using WatchIt.Common.Model.Movies;
|
||||||
using WatchIt.Common.Model.Persons;
|
using WatchIt.Common.Model.Persons;
|
||||||
|
using WatchIt.Common.Model.Photos;
|
||||||
using WatchIt.Common.Model.Series;
|
using WatchIt.Common.Model.Series;
|
||||||
using WatchIt.Common.Services.HttpClient;
|
using WatchIt.Common.Services.HttpClient;
|
||||||
using WatchIt.Website.Services.Configuration;
|
using WatchIt.Website.Services.Configuration;
|
||||||
@@ -71,7 +72,7 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig
|
|||||||
|
|
||||||
public async Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null)
|
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);
|
string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountProfilePicture, id);
|
||||||
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||||
|
|
||||||
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
@@ -81,6 +82,74 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig
|
|||||||
.ExecuteAction();
|
.ExecuteAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task PutAccountProfilePicture(AccountProfilePictureRequest data, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountProfilePicture);
|
||||||
|
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Put, url)
|
||||||
|
{
|
||||||
|
Body = data
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor400BadRequest(badRequestAction)
|
||||||
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAccountProfilePicture(Action? successAction = null, Action? unauthorizedAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Accounts.DeleteAccountProfilePicture);
|
||||||
|
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||||
|
|
||||||
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GetAccountProfileBackground(long id, Action<PhotoResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountProfileBackground, id);
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Get, url);
|
||||||
|
|
||||||
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor400BadRequest(badRequestAction)
|
||||||
|
.RegisterActionFor404NotFound(notFoundAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PutAccountProfileBackground(AccountProfileBackgroundRequest data, Action<PhotoResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountProfileBackground);
|
||||||
|
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Put, url)
|
||||||
|
{
|
||||||
|
Body = data
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor400BadRequest(badRequestAction)
|
||||||
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAccountProfileBackground(Action? successAction = null, Action? unauthorizedAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Accounts.DeleteAccountProfileBackground);
|
||||||
|
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Delete, url);
|
||||||
|
|
||||||
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task GetAccountInfo(long id, Action<AccountResponse>? successAction = null, Action? notFoundAction = null)
|
public async Task GetAccountInfo(long id, Action<AccountResponse>? successAction = null, Action? notFoundAction = null)
|
||||||
{
|
{
|
||||||
string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountInfo, id);
|
string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountInfo, id);
|
||||||
@@ -92,9 +161,9 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig
|
|||||||
.ExecuteAction();
|
.ExecuteAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PutAccountInfo(AccountRequest data, Action<AccountResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null)
|
public async Task PutAccountProfileInfo(AccountProfileInfoRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null)
|
||||||
{
|
{
|
||||||
string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountInfo);
|
string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountProfileInfo);
|
||||||
HttpRequest request = new HttpRequest(HttpMethodType.Put, url)
|
HttpRequest request = new HttpRequest(HttpMethodType.Put, url)
|
||||||
{
|
{
|
||||||
Body = data,
|
Body = data,
|
||||||
@@ -104,7 +173,51 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig
|
|||||||
response.RegisterActionFor2XXSuccess(successAction)
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
.RegisterActionFor400BadRequest(badRequestAction)
|
.RegisterActionFor400BadRequest(badRequestAction)
|
||||||
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
.RegisterActionFor404NotFound(notFoundAction)
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PatchAccountUsername(AccountUsernameRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Accounts.PatchAccountUsername);
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Patch, url)
|
||||||
|
{
|
||||||
|
Body = data,
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor400BadRequest(badRequestAction)
|
||||||
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PatchAccountEmail(AccountEmailRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Accounts.PatchAccountEmail);
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Patch, url)
|
||||||
|
{
|
||||||
|
Body = data,
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor400BadRequest(badRequestAction)
|
||||||
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
|
.ExecuteAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PatchAccountPassword(AccountPasswordRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null)
|
||||||
|
{
|
||||||
|
string url = GetUrl(EndpointsConfiguration.Accounts.PatchAccountPassword);
|
||||||
|
HttpRequest request = new HttpRequest(HttpMethodType.Patch, url)
|
||||||
|
{
|
||||||
|
Body = data,
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpResponse response = await httpClientService.SendRequestAsync(request);
|
||||||
|
response.RegisterActionFor2XXSuccess(successAction)
|
||||||
|
.RegisterActionFor400BadRequest(badRequestAction)
|
||||||
|
.RegisterActionFor401Unauthorized(unauthorizedAction)
|
||||||
.ExecuteAction();
|
.ExecuteAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using WatchIt.Common.Model.Accounts;
|
using WatchIt.Common.Model.Accounts;
|
||||||
using WatchIt.Common.Model.Movies;
|
using WatchIt.Common.Model.Movies;
|
||||||
using WatchIt.Common.Model.Persons;
|
using WatchIt.Common.Model.Persons;
|
||||||
|
using WatchIt.Common.Model.Photos;
|
||||||
using WatchIt.Common.Model.Series;
|
using WatchIt.Common.Model.Series;
|
||||||
|
|
||||||
namespace WatchIt.Website.Services.Client.Accounts;
|
namespace WatchIt.Website.Services.Client.Accounts;
|
||||||
@@ -12,8 +13,16 @@ public interface IAccountsClientService
|
|||||||
Task AuthenticateRefresh(Action<AuthenticateResponse>? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
Task AuthenticateRefresh(Action<AuthenticateResponse>? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
|
||||||
Task Logout(Action? successAction = null);
|
Task Logout(Action? successAction = null);
|
||||||
Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
|
Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
|
||||||
|
Task PutAccountProfilePicture(AccountProfilePictureRequest data, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null);
|
||||||
|
Task DeleteAccountProfilePicture(Action? successAction = null, Action? unauthorizedAction = null);
|
||||||
|
Task GetAccountProfileBackground(long id, Action<PhotoResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
|
||||||
|
Task PutAccountProfileBackground(AccountProfileBackgroundRequest data, Action<PhotoResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null);
|
||||||
|
Task DeleteAccountProfileBackground(Action? successAction = null, Action? unauthorizedAction = null);
|
||||||
Task GetAccountInfo(long id, Action<AccountResponse>? successAction = null, Action? notFoundAction = null);
|
Task GetAccountInfo(long id, Action<AccountResponse>? successAction = null, Action? notFoundAction = null);
|
||||||
Task PutAccountInfo(AccountRequest data, Action<AccountResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null);
|
Task PutAccountProfileInfo(AccountProfileInfoRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null);
|
||||||
|
Task PatchAccountUsername(AccountUsernameRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null);
|
||||||
|
Task PatchAccountEmail(AccountEmailRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null);
|
||||||
|
Task PatchAccountPassword(AccountPasswordRequest data, Action? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null);
|
||||||
Task GetAccountRatedMovies(long id, MovieRatedQueryParameters query, Action<IEnumerable<MovieRatedResponse>>? successAction = null, Action? notFoundAction = null);
|
Task GetAccountRatedMovies(long id, MovieRatedQueryParameters query, Action<IEnumerable<MovieRatedResponse>>? successAction = null, Action? notFoundAction = null);
|
||||||
Task GetAccountRatedSeries(long id, SeriesRatedQueryParameters query, Action<IEnumerable<SeriesRatedResponse>>? successAction = null, Action? notFoundAction = null);
|
Task GetAccountRatedSeries(long id, SeriesRatedQueryParameters query, Action<IEnumerable<SeriesRatedResponse>>? successAction = null, Action? notFoundAction = null);
|
||||||
Task GetAccountRatedPersons(long id, PersonRatedQueryParameters query, Action<IEnumerable<PersonRatedResponse>>? successAction = null, Action? notFoundAction = null);
|
Task GetAccountRatedPersons(long id, PersonRatedQueryParameters query, Action<IEnumerable<PersonRatedResponse>>? successAction = null, Action? notFoundAction = null);
|
||||||
|
|||||||
@@ -7,9 +7,17 @@ public class Accounts
|
|||||||
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 Logout { get; set; }
|
||||||
public string GetProfilePicture { get; set; }
|
public string GetAccountProfilePicture { get; set; }
|
||||||
|
public string PutAccountProfilePicture { get; set; }
|
||||||
|
public string DeleteAccountProfilePicture { get; set; }
|
||||||
|
public string GetAccountProfileBackground { get; set; }
|
||||||
|
public string PutAccountProfileBackground { get; set; }
|
||||||
|
public string DeleteAccountProfileBackground { get; set; }
|
||||||
public string GetAccountInfo { get; set; }
|
public string GetAccountInfo { get; set; }
|
||||||
public string PutAccountInfo { get; set; }
|
public string PutAccountProfileInfo { get; set; }
|
||||||
|
public string PatchAccountUsername { get; set; }
|
||||||
|
public string PatchAccountEmail { get; set; }
|
||||||
|
public string PatchAccountPassword { get; set; }
|
||||||
public string GetAccountRatedMovies { get; set; }
|
public string GetAccountRatedMovies { get; set; }
|
||||||
public string GetAccountRatedSeries { get; set; }
|
public string GetAccountRatedSeries { get; set; }
|
||||||
public string GetAccountRatedPersons { get; set; }
|
public string GetAccountRatedPersons { get; set; }
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
<!-- CSS -->
|
<!-- CSS -->
|
||||||
<link rel="stylesheet" href="css/general.css?version=0.3.0.5"/>
|
<link rel="stylesheet" href="css/general.css?version=0.3.0.5"/>
|
||||||
<link rel="stylesheet" href="css/panel.css?version=0.3.0.3"/>
|
<link rel="stylesheet" href="css/panel.css?version=0.3.0.5"/>
|
||||||
<link rel="stylesheet" href="css/main_button.css?version=0.3.0.0"/>
|
<link rel="stylesheet" href="css/main_button.css?version=0.3.0.0"/>
|
||||||
<link rel="stylesheet" href="css/gaps.css?version=0.3.0.1"/>
|
<link rel="stylesheet" href="css/gaps.css?version=0.3.0.1"/>
|
||||||
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.4.0.13"/>
|
<link rel="stylesheet" href="WatchIt.Website.styles.css?version=0.4.0.17"/>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||||
|
|
||||||
<!-- BOOTSTRAP -->
|
<!-- BOOTSTRAP -->
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{
|
{
|
||||||
<a class="text-reset text-decoration-none" href="@(string.Format(ItemUrlFormatString, IdSource(_items.ElementAt(i))))">
|
<a class="text-reset text-decoration-none" href="@(string.Format(ItemUrlFormatString, IdSource(_items.ElementAt(i))))">
|
||||||
@{int iCopy = i;}
|
@{int iCopy = i;}
|
||||||
<HorizontalListItemComponent Place="@(i + 1)"
|
<HorizontalListItemComponent Place="@(HidePlace ? null : i + 1)"
|
||||||
Name="@(NameSource(_items.ElementAt(iCopy)))"
|
Name="@(NameSource(_items.ElementAt(iCopy)))"
|
||||||
PosterPlaceholder="@(PosterPlaceholder)"
|
PosterPlaceholder="@(PosterPlaceholder)"
|
||||||
GetPosterAction="@(action => GetPictureAction(IdSource(_items.ElementAt(iCopy)), action))"/>
|
GetPosterAction="@(action => GetPictureAction(IdSource(_items.ElementAt(iCopy)), action))"/>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public partial class HorizontalListPanelComponent<TItem> : ComponentBase
|
|||||||
[Parameter] public required Func<TItem, string> NameSource { get; set; }
|
[Parameter] public required Func<TItem, string> NameSource { get; set; }
|
||||||
[Parameter] public required string PosterPlaceholder { get; set; }
|
[Parameter] public required string PosterPlaceholder { get; set; }
|
||||||
[Parameter] public required Func<long, Action<Picture>, Task> GetPictureAction { get; set; }
|
[Parameter] public required Func<long, Action<Picture>, Task> GetPictureAction { get; set; }
|
||||||
|
[Parameter] public bool HidePlace { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@if (_loaded)
|
@if (_loaded)
|
||||||
{
|
{
|
||||||
<div class="vstack gap-3">
|
<div class="vstack gap-3">
|
||||||
<PictureComponent Picture="@(_pictureSelected)" Placeholder="@(PicturePlaceholder)" AlternativeText="poster" Width="@(ContentWidth)" AspectRatio="PictureComponent.PictureComponentAspectRatio.Default"/>
|
<PictureComponent Picture="@(_pictureSelected)" Placeholder="@(PicturePlaceholder)" AlternativeText="loaded_picture" Circle="@(Circle)" Width="@(ContentWidth)"/>
|
||||||
<InputFile class="form-control content-width" OnChange="Load" disabled="@(!Id.HasValue)" autocomplete="off"/>
|
<InputFile class="form-control content-width" OnChange="Load" disabled="@(!Id.HasValue)" autocomplete="off"/>
|
||||||
@if (_pictureChanged || _pictureSaved is not null)
|
@if (_pictureChanged || _pictureSaved is not null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ public partial class PictureEditorPanelComponent : ComponentBase
|
|||||||
[Parameter] public long? Id { get; set; }
|
[Parameter] public long? Id { get; set; }
|
||||||
[Parameter] public int ContentWidth { get; set; } = 300;
|
[Parameter] public int ContentWidth { get; set; } = 300;
|
||||||
[Parameter] public required string PicturePlaceholder { get; set; }
|
[Parameter] public required string PicturePlaceholder { get; set; }
|
||||||
|
[Parameter] public bool Circle { get; set; }
|
||||||
[Parameter] public string Class { get; set; } = string.Empty;
|
[Parameter] public string Class { get; set; } = string.Empty;
|
||||||
[Parameter] public required Func<long, Action<Picture>, Task> PictureGetTask { get; set; }
|
[Parameter] public required Func<long, Action<Picture>, Task> PictureGetTask { get; set; }
|
||||||
[Parameter] public required Func<long, Picture, Action<Picture>, Task> PicturePutTask { get; set; }
|
[Parameter] public required Func<long, Picture, Action<Picture>, Task> PicturePutTask { get; set; }
|
||||||
[Parameter] public required Func<long, Action, Task> PictureDeleteTask { get; set; }
|
[Parameter] public required Func<long, Action, Task> PictureDeleteTask { get; set; }
|
||||||
|
[Parameter] public Action<Picture?>? OnPictureChanged { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -92,6 +94,7 @@ public partial class PictureEditorPanelComponent : ComponentBase
|
|||||||
_pictureSelected = data;
|
_pictureSelected = data;
|
||||||
_pictureChanged = false;
|
_pictureChanged = false;
|
||||||
_pictureSaving = false;
|
_pictureSaving = false;
|
||||||
|
OnPictureChanged?.Invoke(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
_pictureSaving = true;
|
_pictureSaving = true;
|
||||||
@@ -112,6 +115,7 @@ public partial class PictureEditorPanelComponent : ComponentBase
|
|||||||
_pictureSelected = null;
|
_pictureSelected = null;
|
||||||
_pictureChanged = false;
|
_pictureChanged = false;
|
||||||
_pictureDeleting = false;
|
_pictureDeleting = false;
|
||||||
|
OnPictureChanged?.Invoke(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_pictureDeleting = true;
|
_pictureDeleting = true;
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
<img class="rounded-circle object-fit-cover @(Class)" alt="avatar" height="@(Size)" src="@(_picture is null ? "assets/user_placeholder.png" : _picture.ToString())"/>
|
<PictureComponent Class="@(Class)"
|
||||||
|
Height="Size"
|
||||||
|
Circle="true"
|
||||||
|
Shadow="false"
|
||||||
|
Placeholder="assets/user_placeholder.png"
|
||||||
|
AlternativeText="avatar"
|
||||||
|
Picture="@(_picture)"/>
|
||||||
@@ -33,24 +33,25 @@ public partial class AccountPictureComponent : ComponentBase
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public async Task Reload()
|
||||||
|
{
|
||||||
|
await AccountsClientService.GetAccountProfilePicture(Id, data => _picture = data, notFoundAction: () => _picture = null);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PRIVATE METHODS
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
List<Task> endTasks = new List<Task>();
|
await Reload();
|
||||||
|
|
||||||
// STEP 0
|
|
||||||
endTasks.AddRange(
|
|
||||||
[
|
|
||||||
AccountsClientService.GetAccountProfilePicture(Id, data => _picture = data)
|
|
||||||
]);
|
|
||||||
|
|
||||||
// END
|
|
||||||
await Task.WhenAll(endTasks);
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,16 @@
|
|||||||
<PictureComponent Class="w-100" Picture="@(_poster)" Placeholder="@(PosterPlaceholder)" AlternativeText="poster"/>
|
<PictureComponent Class="w-100" Picture="@(_poster)" Placeholder="@(PosterPlaceholder)" AlternativeText="poster"/>
|
||||||
<div class="container-grid">
|
<div class="container-grid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto">
|
@if (Place.HasValue)
|
||||||
<div class="text-center border border-2 border-light rounded-circle place-circle fw-bold">@(Place)</div>
|
{
|
||||||
</div>
|
<div class="col-auto">
|
||||||
|
<div class="text-center border border-2 border-light rounded-circle place-circle fw-bold">@(Place)</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="text-end ms-auto pt-05">@(Name)</div>
|
<div class="d-flex justify-content-@(Place.HasValue ? "end" : "center")">
|
||||||
|
<div class="pt-05 @(Place.HasValue ? "text-end" : "text-center")">@(Name)</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public partial class HorizontalListItemComponent : ComponentBase
|
|||||||
{
|
{
|
||||||
#region PARAMETERS
|
#region PARAMETERS
|
||||||
|
|
||||||
[Parameter] public required int Place { get; set; }
|
[Parameter] public int? Place { get; set; }
|
||||||
[Parameter] public required string Name { get; set; }
|
[Parameter] public required string Name { get; set; }
|
||||||
[Parameter] public required string PosterPlaceholder { get; set; }
|
[Parameter] public required string PosterPlaceholder { get; set; }
|
||||||
[Parameter] public required Func<Action<Picture>, Task> GetPosterAction { get; set; }
|
[Parameter] public required Func<Action<Picture>, Task> GetPosterAction { get; set; }
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<img id="imgObject" class="rounded-2 shadow object-fit-cover @(Class)" src="@(Picture is not null ? Picture.ToString() : Placeholder)" alt="@(AlternativeText)" @attributes="@(_attributes)" style="aspect-ratio: @(AspectRatio.ToString());"/>
|
<img class="@(Circle ? "rounded-circle" : "rounded-2") @(Shadow ? "shadow" : string.Empty) object-fit-cover @(Class)" src="@(Picture is not null ? Picture.ToString() : Placeholder)" alt="@(AlternativeText)" @attributes="@(_attributes)" style="aspect-ratio: @(AspectRatio.ToString());"/>
|
||||||
@@ -14,6 +14,8 @@ public partial class PictureComponent : ComponentBase
|
|||||||
[Parameter] public string Class { get; set; } = string.Empty;
|
[Parameter] public string Class { get; set; } = string.Empty;
|
||||||
[Parameter] public int? Height { get; set; }
|
[Parameter] public int? Height { get; set; }
|
||||||
[Parameter] public int? Width { get; set; }
|
[Parameter] public int? Width { get; set; }
|
||||||
|
[Parameter] public bool Circle { get; set; }
|
||||||
|
[Parameter] public bool Shadow { get; set; } = true;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -40,6 +42,11 @@ public partial class PictureComponent : ComponentBase
|
|||||||
{
|
{
|
||||||
_attributes.Add("width", Width.Value);
|
_attributes.Add("width", Width.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Circle)
|
||||||
|
{
|
||||||
|
AspectRatio = PictureComponentAspectRatio.Square;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -71,6 +78,7 @@ public partial class PictureComponent : ComponentBase
|
|||||||
|
|
||||||
public static readonly PictureComponentAspectRatio Default = new PictureComponentAspectRatio();
|
public static readonly PictureComponentAspectRatio Default = new PictureComponentAspectRatio();
|
||||||
public static readonly PictureComponentAspectRatio Photo = new PictureComponentAspectRatio(16, 9);
|
public static readonly PictureComponentAspectRatio Photo = new PictureComponentAspectRatio(16, 9);
|
||||||
|
public static readonly PictureComponentAspectRatio Square = new PictureComponentAspectRatio(1, 1);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="panel panel-section-header">
|
||||||
|
<div class="d-flex">
|
||||||
|
<h3 class="fw-bold m-0">Account settings</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="panel">
|
||||||
|
<div class="vstack gap-3">
|
||||||
|
<h4 class="fw-bold">Change email</h4>
|
||||||
|
@if (_data is not null)
|
||||||
|
{
|
||||||
|
<EditForm Model="@(_data)">
|
||||||
|
<AntiforgeryToken/>
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="email" class="col-2 col-form-label">New email</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputText id="email" class="form-control" @bind-Value="_data!.NewEmail"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="password" class="col-2 col-form-label">Password</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputText id="password" type="password" class="form-control" @bind-Value="_data!.Password"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col align-self-center">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_error))
|
||||||
|
{
|
||||||
|
<span class="text-danger">@(_error)</span>
|
||||||
|
}
|
||||||
|
else if (_saved)
|
||||||
|
{
|
||||||
|
<span class="text-success">New email saved!</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button type="submit" class="btn btn-secondary" disabled="@(_saving)" @onclick="@(Save)">
|
||||||
|
<LoadingButtonContentComponent IsLoading="@(_saving)" Content="Save" LoadingContent="Saving..."/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<LoadingComponent Color="@(LoadingComponent.LoadingComponentColors.Light)"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Website.Services.Client.Accounts;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Components.Pages.UserEditPage.Panels;
|
||||||
|
|
||||||
|
public partial class NewEmailPanelComponent : ComponentBase
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] private IAccountsClientService AccountsClientService { get; set; } = default!;
|
||||||
|
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[Parameter] public required AccountResponse AccountData { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private AccountEmailRequest? _data;
|
||||||
|
private string? _error;
|
||||||
|
private bool _saving;
|
||||||
|
private bool _saved;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
_data = new AccountEmailRequest
|
||||||
|
{
|
||||||
|
NewEmail = AccountData.Email,
|
||||||
|
};
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
void Success()
|
||||||
|
{
|
||||||
|
_saved = true;
|
||||||
|
_saving = false;
|
||||||
|
_data = new AccountEmailRequest
|
||||||
|
{
|
||||||
|
NewEmail = _data!.NewEmail
|
||||||
|
};
|
||||||
|
NavigationManager.Refresh(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BadRequest(IDictionary<string, string[]> errors)
|
||||||
|
{
|
||||||
|
_error = errors.SelectMany(x => x.Value).FirstOrDefault() ?? "Unknown error";
|
||||||
|
_saving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unauthorized()
|
||||||
|
{
|
||||||
|
_error = "Incorrect password";
|
||||||
|
_saving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_saving = true;
|
||||||
|
_saved = false;
|
||||||
|
_error = null;
|
||||||
|
await AccountsClientService.PatchAccountEmail(_data!, Success, BadRequest, Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<div class="panel">
|
||||||
|
<div class="vstack gap-3">
|
||||||
|
<h4 class="fw-bold">Change password</h4>
|
||||||
|
<EditForm Model="@(_data)">
|
||||||
|
<AntiforgeryToken/>
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="oldPassword" class="col-2 col-form-label">Old password</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputText id="oldPassword" class="form-control" @bind-Value="_data.OldPassword"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="newPassword" class="col-2 col-form-label">New password</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputText id="newPassword" type="password" class="form-control" @bind-Value="_data.NewPassword"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="newPasswordConf" class="col-2 col-form-label">Confirm new password</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputText id="newPasswordConf" type="password" class="form-control" @bind-Value="_data.NewPasswordConfirmation"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col align-self-center">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_error))
|
||||||
|
{
|
||||||
|
<span class="text-danger">@(_error)</span>
|
||||||
|
}
|
||||||
|
else if (_saved)
|
||||||
|
{
|
||||||
|
<span class="text-success">New email saved!</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button type="submit" class="btn btn-secondary" disabled="@(_saving)" @onclick="@(Save)">
|
||||||
|
<LoadingButtonContentComponent IsLoading="@(_saving)" Content="Save" LoadingContent="Saving..."/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Website.Services.Client.Accounts;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Components.Pages.UserEditPage.Panels;
|
||||||
|
|
||||||
|
public partial class NewPasswordPanelComponent : ComponentBase
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] private IAccountsClientService AccountsClientService { get; set; } = default!;
|
||||||
|
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private AccountPasswordRequest _data = new AccountPasswordRequest();
|
||||||
|
private string? _error;
|
||||||
|
private bool _saving;
|
||||||
|
private bool _saved;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
void Success()
|
||||||
|
{
|
||||||
|
_saved = true;
|
||||||
|
_saving = false;
|
||||||
|
_data = new AccountPasswordRequest();
|
||||||
|
NavigationManager.Refresh(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BadRequest(IDictionary<string, string[]> errors)
|
||||||
|
{
|
||||||
|
_error = errors.SelectMany(x => x.Value).FirstOrDefault() ?? "Unknown error";
|
||||||
|
_saving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unauthorized()
|
||||||
|
{
|
||||||
|
_error = "Incorrect password";
|
||||||
|
_saving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_saving = true;
|
||||||
|
_saved = false;
|
||||||
|
_error = null;
|
||||||
|
await AccountsClientService.PatchAccountPassword(_data, Success, BadRequest, Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="panel">
|
||||||
|
<div class="vstack gap-3">
|
||||||
|
<h4 class="fw-bold">Change username</h4>
|
||||||
|
@if (_data is not null)
|
||||||
|
{
|
||||||
|
<EditForm Model="@(_data)">
|
||||||
|
<AntiforgeryToken/>
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="username" class="col-2 col-form-label">New username</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputText id="username" class="form-control" @bind-Value="_data!.NewUsername"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="password" class="col-2 col-form-label">Password</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputText id="password" type="password" class="form-control" @bind-Value="_data!.Password"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col align-self-center">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_error))
|
||||||
|
{
|
||||||
|
<span class="text-danger">@(_error)</span>
|
||||||
|
}
|
||||||
|
else if (_saved)
|
||||||
|
{
|
||||||
|
<span class="text-success">New username saved!</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button type="submit" class="btn btn-secondary" disabled="@(_saving)" @onclick="@(Save)">
|
||||||
|
<LoadingButtonContentComponent IsLoading="@(_saving)" Content="Save" LoadingContent="Saving..."/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<LoadingComponent Color="@(LoadingComponent.LoadingComponentColors.Light)"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Website.Services.Authentication;
|
||||||
|
using WatchIt.Website.Services.Client.Accounts;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Components.Pages.UserEditPage.Panels;
|
||||||
|
|
||||||
|
public partial class NewUsernamePanelComponent : ComponentBase
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] private IAccountsClientService AccountsClientService { get; set; } = default!;
|
||||||
|
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[Parameter] public required AccountResponse AccountData { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private AccountUsernameRequest? _data;
|
||||||
|
private string? _error;
|
||||||
|
private bool _saving;
|
||||||
|
private bool _saved;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
_data = new AccountUsernameRequest
|
||||||
|
{
|
||||||
|
NewUsername = AccountData.Username,
|
||||||
|
};
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
void Success()
|
||||||
|
{
|
||||||
|
_saved = true;
|
||||||
|
_saving = false;
|
||||||
|
_data = new AccountUsernameRequest
|
||||||
|
{
|
||||||
|
NewUsername = _data!.NewUsername
|
||||||
|
};
|
||||||
|
NavigationManager.Refresh(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BadRequest(IDictionary<string, string[]> errors)
|
||||||
|
{
|
||||||
|
_error = errors.SelectMany(x => x.Value).FirstOrDefault() ?? "Unknown error";
|
||||||
|
_saving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unauthorized()
|
||||||
|
{
|
||||||
|
_error = "Incorrect password";
|
||||||
|
_saving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_saving = true;
|
||||||
|
_saved = false;
|
||||||
|
_error = null;
|
||||||
|
await AccountsClientService.PatchAccountUsername(_data!, Success, BadRequest, Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
@using Blazorise.Components
|
||||||
|
@using WatchIt.Common.Model.Photos
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
@if (_loaded)
|
||||||
|
{
|
||||||
|
<div class="vstack gap-4">
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<h4 class="me-auto m-0 fw-bold">Profile background</h4>
|
||||||
|
@if (_editMode)
|
||||||
|
{
|
||||||
|
<button class="btn btn-danger" @onclick="@(Cancel)">Cancel</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button class="btn btn-secondary" @onclick="@(Edit)">Edit</button>
|
||||||
|
<button class="btn btn-danger" @onclick="@(Remove)">
|
||||||
|
<LoadingButtonContentComponent LoadingContent="Removing..." Content="Remove" IsLoading="@(_removeLoading)"/>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (_editMode)
|
||||||
|
{
|
||||||
|
<div class="vstack gap-3">
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row gx-2">
|
||||||
|
<div class="col">
|
||||||
|
<Autocomplete ElementId="actorFormMedia"
|
||||||
|
TItem="MediaResponse"
|
||||||
|
TValue="long?"
|
||||||
|
Data="@(_mediaList)"
|
||||||
|
TextField="@(item => item.ReleaseDate.HasValue ? $"{item.Title} ({item.ReleaseDate.Value.Year})" : item.Title)"
|
||||||
|
ValueField="@(item => item.Id)"
|
||||||
|
@bind-SelectedValue="@(_selectedMedia)"
|
||||||
|
Placeholder="Search media..."
|
||||||
|
Filter="AutocompleteFilter.Contains">
|
||||||
|
<NotFoundContent Context="not_found_context"> Sorry... @not_found_context was not found</NotFoundContent>
|
||||||
|
</Autocomplete>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button class="btn btn-secondary" disabled="@(_backgroundsLoading)" @onclick="@(LoadBackgrounds)">
|
||||||
|
<LoadingButtonContentComponent LoadingContent="Loading..." Content="Load backgrounds" IsLoading="@(_backgroundsLoading)"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_mediaPhotos is null)
|
||||||
|
{
|
||||||
|
<span class="text-center">Select media first</span>
|
||||||
|
}
|
||||||
|
else if (!_mediaPhotos.Any())
|
||||||
|
{
|
||||||
|
<span class="text-center">No backgrounds for this media</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div id="scrollPhotos" class="d-flex p-3 gap-3" data-bs-spy="scroll" tabindex="0">
|
||||||
|
@foreach (PhotoResponse photo in _mediaPhotos)
|
||||||
|
{
|
||||||
|
<div class="photo-container">
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col">
|
||||||
|
<PictureComponent Picture="@(photo)" AlternativeText="photo" Width="350" Placeholder="/assets/photo.png" AspectRatio="PictureComponent.PictureComponentAspectRatio.Photo"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row gx-2">
|
||||||
|
<div class="col">
|
||||||
|
<div class="border rounded-3" style="height: 30px; background: linear-gradient(45deg, @($"#{Convert.ToHexString(photo.Background!.FirstGradientColor)}, #{Convert.ToHexString(photo.Background!.SecondGradientColor)}"));"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" disabled="@(_saveLoading)" @onclick="@(async () => await Save(photo.Id))">
|
||||||
|
<LoadingButtonContentComponent LoadingContent="Saving..." Content="Select" IsLoading="@(_saveLoading)"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_selectedPhoto is not null)
|
||||||
|
{
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row gx-3 mb-2">
|
||||||
|
<div class="col">
|
||||||
|
<PictureComponent Class="w-100" Picture="@(_selectedPhoto)" AlternativeText="background" Placeholder="/assets/photo.png" AspectRatio="PictureComponent.PictureComponentAspectRatio.Photo"/>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="rounded-3 border h-100" style="height: 30px; background: linear-gradient(45deg, @($"#{Convert.ToHexString(_selectedPhoto.Background!.FirstGradientColor)}, #{Convert.ToHexString(_selectedPhoto.Background!.SecondGradientColor)}"));"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<a class="text-decoration-none text-reset" href="/media/@(_selectedPhotoMedia!.Id)">
|
||||||
|
from <span class="fw-bold">@(_selectedPhotoMedia.Title)</span>@(_selectedPhotoMedia.ReleaseDate.HasValue ? $" ({_selectedPhotoMedia.ReleaseDate.Value.Year})" : string.Empty)
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-center">You don't have selected background. Click "Edit" to choose one.</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<LoadingComponent Color="@(LoadingComponent.LoadingComponentColors.Light)"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Common.Model.Media;
|
||||||
|
using WatchIt.Common.Model.Photos;
|
||||||
|
using WatchIt.Website.Services.Authentication;
|
||||||
|
using WatchIt.Website.Services.Client.Accounts;
|
||||||
|
using WatchIt.Website.Services.Client.Media;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Components.Pages.UserEditPage.Panels;
|
||||||
|
|
||||||
|
public partial class ProfileBackgroundEditorPanelComponent : ComponentBase
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] private IMediaClientService MediaClientService { get; set; } = default!;
|
||||||
|
[Inject] private IAccountsClientService AccountsClientService { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[Parameter] public required long Id { get; set; }
|
||||||
|
[Parameter] public Action<PhotoResponse?>? OnBackgroundChanged { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private bool _loaded;
|
||||||
|
|
||||||
|
private bool _editMode;
|
||||||
|
private long? _selectedMedia;
|
||||||
|
private IEnumerable<PhotoResponse>? _mediaPhotos;
|
||||||
|
private bool _backgroundsLoading;
|
||||||
|
private bool _saveLoading;
|
||||||
|
|
||||||
|
private bool _removeLoading;
|
||||||
|
|
||||||
|
private IEnumerable<MediaResponse> _mediaList = default!;
|
||||||
|
|
||||||
|
private PhotoResponse? _selectedPhoto;
|
||||||
|
private MediaResponse? _selectedPhotoMedia;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
await Task.WhenAll(
|
||||||
|
[
|
||||||
|
MediaClientService.GetAllMedia(successAction: data => _mediaList = data),
|
||||||
|
AccountsClientService.GetAccountProfileBackground(Id, data => _selectedPhoto = data)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (_selectedPhoto is not null)
|
||||||
|
{
|
||||||
|
_selectedPhotoMedia = _mediaList.First(x => x.Id == _selectedPhoto.MediaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
_loaded = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save(Guid id)
|
||||||
|
{
|
||||||
|
_saveLoading = true;
|
||||||
|
await AccountsClientService.PutAccountProfileBackground(new AccountProfileBackgroundRequest(id), data =>
|
||||||
|
{
|
||||||
|
OnBackgroundChanged?.Invoke(data);
|
||||||
|
_selectedPhoto = data;
|
||||||
|
_selectedPhotoMedia = _mediaList.First(x => x.Id == _selectedMedia!.Value);
|
||||||
|
_saveLoading = false;
|
||||||
|
Cancel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel()
|
||||||
|
{
|
||||||
|
_editMode = false;
|
||||||
|
_selectedMedia = default;
|
||||||
|
_saveLoading = false;
|
||||||
|
_backgroundsLoading = false;
|
||||||
|
_mediaPhotos = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Edit()
|
||||||
|
{
|
||||||
|
_editMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Remove()
|
||||||
|
{
|
||||||
|
_removeLoading = true;
|
||||||
|
await AccountsClientService.DeleteAccountProfileBackground(() =>
|
||||||
|
{
|
||||||
|
OnBackgroundChanged?.Invoke(null);
|
||||||
|
_selectedPhoto = null;
|
||||||
|
_selectedPhotoMedia = null;
|
||||||
|
_removeLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadBackgrounds()
|
||||||
|
{
|
||||||
|
_backgroundsLoading = true;
|
||||||
|
PhotoQueryParameters query = new PhotoQueryParameters
|
||||||
|
{
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
await MediaClientService.GetMediaPhotos(_selectedMedia!.Value, query, successAction: data =>
|
||||||
|
{
|
||||||
|
_mediaPhotos = data;
|
||||||
|
_backgroundsLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/* IDS */
|
||||||
|
|
||||||
|
#scrollPhotos {
|
||||||
|
overflow-x: scroll;
|
||||||
|
|
||||||
|
border-color: grey;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#backgroundIndicator {
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* CLASSES */
|
||||||
|
|
||||||
|
.photo-container {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
@using WatchIt.Common.Model.Genders
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="panel @(Class)">
|
||||||
|
@if (_loaded)
|
||||||
|
{
|
||||||
|
<div class="vstack gap-3">
|
||||||
|
<h4 class="fw-bold">Basic profile info</h4>
|
||||||
|
<EditForm Model="@(_accountProfileInfo)">
|
||||||
|
<AntiforgeryToken/>
|
||||||
|
<div class="container-grid">
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="desc" class="col-2 col-form-label">Description</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputTextArea id="desc" class="form-control" @bind-Value="_accountProfileInfo!.Description"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row form-group my-1">
|
||||||
|
<label for="desc" class="col-2 col-form-label">Gender</label>
|
||||||
|
<div class="col-10">
|
||||||
|
<InputSelect TValue="short?" id="desc" class="form-control" @bind-Value="_accountProfileInfo!.GenderId">
|
||||||
|
<option value="@(default(short?))">No choice</option>
|
||||||
|
@foreach (GenderResponse gender in _genders)
|
||||||
|
{
|
||||||
|
<option value="@(gender.Id)">@(gender.Name)</option>
|
||||||
|
}
|
||||||
|
</InputSelect>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col align-self-center">
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_error))
|
||||||
|
{
|
||||||
|
<span class="text-danger">@(_error)</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button type="submit" class="btn btn-secondary" disabled="@(_saving)" @onclick="@(Save)">
|
||||||
|
@if (!_saving)
|
||||||
|
{
|
||||||
|
<span>Save</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
|
<span>Saving...</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<LoadingComponent Color="@(LoadingComponent.LoadingComponentColors.Light)"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Common.Model.Genders;
|
||||||
|
using WatchIt.Website.Services.Client.Accounts;
|
||||||
|
using WatchIt.Website.Services.Client.Genders;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Components.Pages.UserEditPage.Panels;
|
||||||
|
|
||||||
|
public partial class ProfileEditFormPanelComponent : ComponentBase
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||||
|
[Inject] private IAccountsClientService AccountsClientService { get; set; } = default!;
|
||||||
|
[Inject] private IGendersClientService GendersClientService { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[Parameter] public required AccountResponse AccountData { get; set; }
|
||||||
|
[Parameter] public string Class { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private bool _loaded;
|
||||||
|
private bool _saving;
|
||||||
|
private string? _error;
|
||||||
|
|
||||||
|
private IEnumerable<GenderResponse> _genders = [];
|
||||||
|
|
||||||
|
private AccountProfileInfoRequest _accountProfileInfo = new AccountProfileInfoRequest();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
_accountProfileInfo = new AccountProfileInfoRequest(AccountData);
|
||||||
|
await GendersClientService.GetAllGenders(successAction: data => _genders = data);
|
||||||
|
|
||||||
|
_loaded = true;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
void Success()
|
||||||
|
{
|
||||||
|
_error = null;
|
||||||
|
_saving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BadRequest(IDictionary<string, string[]> errors)
|
||||||
|
{
|
||||||
|
_error = errors.SelectMany(x => x.Value).FirstOrDefault() ?? "Unknown error";
|
||||||
|
_saving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthError()
|
||||||
|
{
|
||||||
|
_error = "Authentication error";
|
||||||
|
_saving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_saving = true;
|
||||||
|
await AccountsClientService.PutAccountProfileInfo(_accountProfileInfo, Success, BadRequest, AuthError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="panel panel-section-header">
|
||||||
|
<div class="d-flex">
|
||||||
|
<h3 class="fw-bold m-0">Profile settings</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<div class="panel" role="button" @onclick="@(() => NavigationManager.NavigateTo("/user"))">
|
||||||
|
<div class="d-flex gap-3 align-items-center">
|
||||||
|
<AccountPictureComponent @ref="_accountPicture" Id="@(AccountData.Id)" Size="60"/>
|
||||||
|
<div class="d-flex-inline flex-column">
|
||||||
|
<h2 id="username" class="fw-bold m-0">@(AccountData.Username)</h2>
|
||||||
|
<span id="secondaryText" class="text-secondary">User settings</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Website.Components.Common.Subcomponents;
|
||||||
|
using WatchIt.Website.Services.Authentication;
|
||||||
|
using WatchIt.Website.Services.Client.Accounts;
|
||||||
|
|
||||||
|
namespace WatchIt.Website.Components.Pages.UserEditPage.Panels;
|
||||||
|
|
||||||
|
public partial class UserEditPageHeaderPanelComponent : ComponentBase
|
||||||
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] public IAccountsClientService AccountsClientService { get; set; } = default!;
|
||||||
|
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[Parameter] public required AccountResponse AccountData { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private AccountPictureComponent _accountPicture = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public async Task ReloadPicture() => await _accountPicture.Reload();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/* IDS */
|
||||||
|
|
||||||
|
#username {
|
||||||
|
margin-top: -8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#secondaryText {
|
||||||
|
color: lightgray !important;
|
||||||
|
}
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
<div id="base" class="vstack">
|
<div id="base" class="vstack">
|
||||||
<AccountPictureComponent Class="shadow position-absolute z-1 start-50 translate-middle" Id="@(AccountData.Id)" Size="240"/>
|
<AccountPictureComponent Class="shadow position-absolute z-1 start-50 translate-middle" Id="@(AccountProfileInfoData.Id)" Size="240"/>
|
||||||
<div class="panel z-0">
|
<div class="panel z-0">
|
||||||
<div class="vstack gap-3">
|
<div class="vstack gap-3">
|
||||||
<div id="space" class="container-grid"></div>
|
<div id="space" class="container-grid"></div>
|
||||||
<div class="d-flex justify-content-center">
|
<div class="d-flex justify-content-center">
|
||||||
<h3 class="fw-bold">@(AccountData.Username)</h3>
|
<h3 class="fw-bold m-0">@(AccountProfileInfoData.Username)</h3>
|
||||||
</div>
|
</div>
|
||||||
@if (!string.IsNullOrWhiteSpace(AccountData.Description))
|
@if (!string.IsNullOrWhiteSpace(AccountProfileInfoData.Description))
|
||||||
{
|
{
|
||||||
<span>
|
<span class="text-center w-100 mb-2">
|
||||||
@(AccountData.Description)
|
@(AccountProfileInfoData.Description)
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
<div class="d-flex flex-wrap justify-content-center metadata-pill-container">
|
<div class="d-flex flex-wrap justify-content-center metadata-pill-container">
|
||||||
<div class="metadata-pill"><strong>Email:</strong> @(AccountData.Email)</div>
|
<div class="metadata-pill"><strong>Email:</strong> @(AccountProfileInfoData.Email)</div>
|
||||||
@if (!string.IsNullOrWhiteSpace(AccountData.Gender?.Name))
|
@if (!string.IsNullOrWhiteSpace(AccountProfileInfoData.Gender?.Name))
|
||||||
{
|
{
|
||||||
<div class="metadata-pill"><strong>Gender:</strong> @(AccountData.Gender?.Name)</div>
|
<div class="metadata-pill"><strong>Gender:</strong> @(AccountProfileInfoData.Gender?.Name)</div>
|
||||||
}
|
}
|
||||||
<div class="metadata-pill"><strong>Account created:</strong> @(AccountData.CreationDate.ToShortDateString())</div>
|
<div class="metadata-pill"><strong>Account created:</strong> @(AccountProfileInfoData.CreationDate.ToShortDateString())</div>
|
||||||
<div class="metadata-pill"><strong>Last active:</strong> @(AccountData.LastActive.ToShortDateString())</div>
|
<div class="metadata-pill"><strong>Last active:</strong> @(AccountProfileInfoData.LastActive.ToShortDateString())</div>
|
||||||
@if (AccountData.IsAdmin)
|
@if (AccountProfileInfoData.IsAdmin)
|
||||||
{
|
{
|
||||||
<div class="metadata-pill"><strong>Admin</strong></div>
|
<div class="metadata-pill"><strong>Admin</strong></div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public partial class UserPageHeaderPanelComponent : ComponentBase
|
|||||||
|
|
||||||
#region PARAMETERS
|
#region PARAMETERS
|
||||||
|
|
||||||
[Parameter] public required AccountResponse AccountData { get; set; }
|
[Parameter] public required AccountResponse AccountProfileInfoData { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ public partial class UserPageHeaderPanelComponent : ComponentBase
|
|||||||
// STEP 0
|
// STEP 0
|
||||||
endTasks.AddRange(
|
endTasks.AddRange(
|
||||||
[
|
[
|
||||||
AccountsClientService.GetAccountProfilePicture(AccountData.Id, data => _accountProfilePicture = data),
|
AccountsClientService.GetAccountProfilePicture(AccountProfileInfoData.Id, data => _accountProfilePicture = data),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// END
|
// END
|
||||||
|
|||||||
@@ -55,13 +55,14 @@
|
|||||||
<Dropdown RightAligned>
|
<Dropdown RightAligned>
|
||||||
<Button Color="Color.Default" Clicked="@(() => NavigationManager.NavigateTo("/user"))">
|
<Button Color="Color.Default" Clicked="@(() => NavigationManager.NavigateTo("/user"))">
|
||||||
<div class="d-flex gap-2 align-items-center">
|
<div class="d-flex gap-2 align-items-center">
|
||||||
<AccountPictureComponent Id="@(_user.Id)" Size="30"/>
|
<AccountPictureComponent @ref="_profilePicture" Id="@(_user.Id)" Size="30"/>
|
||||||
<span>@(_user.Username)</span>
|
<span>@(_accountData!.Username)</span>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
<DropdownToggle Color="Color.Default" Split />
|
<DropdownToggle Color="Color.Default" Split />
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownItem Clicked="@(() => NavigationManager.NavigateTo("/user"))">Your profile</DropdownItem>
|
<DropdownItem Clicked="@(() => NavigationManager.NavigateTo("/user"))">Your profile</DropdownItem>
|
||||||
|
<DropdownItem Clicked="@(() => NavigationManager.NavigateTo("/user/edit"))">User settings</DropdownItem>
|
||||||
@if (_user.IsAdmin)
|
@if (_user.IsAdmin)
|
||||||
{
|
{
|
||||||
<DropdownDivider/>
|
<DropdownDivider/>
|
||||||
@@ -79,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row pb-3">
|
<div class="row pt-3 pb-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@Body
|
@Body
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Net;
|
|||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using WatchIt.Common.Model.Accounts;
|
using WatchIt.Common.Model.Accounts;
|
||||||
using WatchIt.Common.Model.Photos;
|
using WatchIt.Common.Model.Photos;
|
||||||
|
using WatchIt.Website.Components.Common.Subcomponents;
|
||||||
using WatchIt.Website.Services.Authentication;
|
using WatchIt.Website.Services.Authentication;
|
||||||
using WatchIt.Website.Services.Tokens;
|
using WatchIt.Website.Services.Tokens;
|
||||||
using WatchIt.Website.Services.Client.Accounts;
|
using WatchIt.Website.Services.Client.Accounts;
|
||||||
@@ -20,6 +21,7 @@ public partial class MainLayout : LayoutComponentBase
|
|||||||
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
[Inject] public IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||||
[Inject] public IMediaClientService MediaClientService { get; set; } = default!;
|
[Inject] public IMediaClientService MediaClientService { get; set; } = default!;
|
||||||
[Inject] public IPhotosClientService PhotosClientService { get; set; } = default!;
|
[Inject] public IPhotosClientService PhotosClientService { get; set; } = default!;
|
||||||
|
[Inject] public IAccountsClientService AccountsClientService { get; set; } = default!;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -27,9 +29,12 @@ public partial class MainLayout : LayoutComponentBase
|
|||||||
|
|
||||||
#region FIELDS
|
#region FIELDS
|
||||||
|
|
||||||
|
private AccountPictureComponent? _profilePicture;
|
||||||
|
|
||||||
private bool _loaded;
|
private bool _loaded;
|
||||||
|
|
||||||
private User? _user;
|
private User? _user;
|
||||||
|
private AccountResponse? _accountData;
|
||||||
private PhotoResponse? _defaultBackgroundPhoto;
|
private PhotoResponse? _defaultBackgroundPhoto;
|
||||||
|
|
||||||
private bool _searchbarVisible;
|
private bool _searchbarVisible;
|
||||||
@@ -56,6 +61,20 @@ public partial class MainLayout : LayoutComponentBase
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PUBLIC METHODS
|
||||||
|
|
||||||
|
public async Task ReloadProfilePicture()
|
||||||
|
{
|
||||||
|
if (_profilePicture is not null)
|
||||||
|
{
|
||||||
|
await _profilePicture.Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region PRIVATE METHODS
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
#region Main
|
#region Main
|
||||||
@@ -64,17 +83,16 @@ public partial class MainLayout : LayoutComponentBase
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
List<Task> endTasks = new List<Task>();
|
await Task.WhenAll(
|
||||||
|
|
||||||
// STEP 0
|
|
||||||
endTasks.AddRange(
|
|
||||||
[
|
[
|
||||||
Task.Run(async () => _user = await AuthenticationService.GetUserAsync()),
|
Task.Run(async () => _user = await AuthenticationService.GetUserAsync()),
|
||||||
PhotosClientService.GetPhotoRandomBackground(data => _defaultBackgroundPhoto = data)
|
PhotosClientService.GetPhotoRandomBackground(data => _defaultBackgroundPhoto = data)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// END
|
if (_user is not null)
|
||||||
await Task.WhenAll(endTasks);
|
{
|
||||||
|
await AccountsClientService.GetAccountInfo(_user.Id, data => _accountData = data);
|
||||||
|
}
|
||||||
|
|
||||||
_loaded = true;
|
_loaded = true;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|||||||
@@ -51,7 +51,18 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="rounded-3 panel panel-regular p-2">
|
<div class="rounded-3 panel panel-regular p-2">
|
||||||
<h3 class="m-0 mx-2 mb-1 p-0">@(_media is not null ? "Edit" : "Create new") @(_movieRequest is not null ? "movie" : "series")@(_media is not null ? $" \"{_media.Title}\"" : string.Empty)</h3>
|
<div class="m-0 mx-2 mb-1 p-0">
|
||||||
|
@if (_media is not null)
|
||||||
|
{
|
||||||
|
<a class="text-decoration-none text-reset" href="/media/@(_media.Id)">
|
||||||
|
<h3>Edit @(_movieRequest is not null ? "movie" : "series") "@(_media.Title)"</h3>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<h3>Create new @(_movieRequest is not null ? "movie" : "series")</h3>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1 +1,93 @@
|
|||||||
|
@using System.Text
|
||||||
|
@using WatchIt.Common.Model
|
||||||
|
@using WatchIt.Common.Model.Photos
|
||||||
|
@using WatchIt.Website.Components.Pages.UserEditPage.Panels
|
||||||
|
|
||||||
|
|
||||||
@page "/user/edit"
|
@page "/user/edit"
|
||||||
|
|
||||||
|
@{
|
||||||
|
StringBuilder sb = new StringBuilder(" - WatchIt");
|
||||||
|
|
||||||
|
if (_accountData is null) sb.Insert(0, "Loading...");
|
||||||
|
else sb.Insert(0, "User settings");
|
||||||
|
|
||||||
|
<PageTitle>@(sb.ToString())</PageTitle>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container-grid">
|
||||||
|
@if (_accountData is not null)
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<UserEditPageHeaderPanelComponent @ref="@(_header)" AccountData="@(_accountData)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-default">
|
||||||
|
<div class="col">
|
||||||
|
<Tabs Pills
|
||||||
|
RenderMode="TabsRenderMode.LazyLoad"
|
||||||
|
Class="panel panel-menu panel-background-menu justify-content-center"
|
||||||
|
SelectedTab="profile">
|
||||||
|
<Items>
|
||||||
|
<Tab Name="profile">Profile</Tab>
|
||||||
|
<Tab Name="account">Account</Tab>
|
||||||
|
</Items>
|
||||||
|
<Content>
|
||||||
|
<TabPanel Name="profile">
|
||||||
|
<div class="container-grid mt-default">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<ProfileEditHeaderPanelComponent/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-default gx-default">
|
||||||
|
<div class="col-auto">
|
||||||
|
<PictureEditorPanelComponent Id="@(_accountData.Id)"
|
||||||
|
Class="h-100"
|
||||||
|
PicturePlaceholder="assets/user_placeholder.png"
|
||||||
|
Circle="true"
|
||||||
|
PictureGetTask="@((id, action) => AccountsClientService.GetAccountProfilePicture(id, action))"
|
||||||
|
PicturePutTask="@((_, picture, action) => AccountsClientService.PutAccountProfilePicture(new AccountProfilePictureRequest(picture), action))"
|
||||||
|
PictureDeleteTask="@((_, action) => AccountsClientService.DeleteAccountProfilePicture(action))"
|
||||||
|
OnPictureChanged="@(async (_) => await PictureChanged())"/>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<ProfileEditFormPanelComponent AccountData="@(_accountData)"
|
||||||
|
Class="h-100"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-default">
|
||||||
|
<div class="col">
|
||||||
|
<ProfileBackgroundEditorPanelComponent Id="@(_accountData.Id)"
|
||||||
|
OnBackgroundChanged="BackgroundChanged"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="account">
|
||||||
|
<div class="vstack mt-default gap-default">
|
||||||
|
<AccountEditHeaderPanelComponent/>
|
||||||
|
<NewUsernamePanelComponent AccountData="@(_accountData)"/>
|
||||||
|
<NewEmailPanelComponent AccountData="@(_accountData)"/>
|
||||||
|
<NewPasswordPanelComponent/>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
</Content>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="m-5">
|
||||||
|
<LoadingComponent/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,80 @@
|
|||||||
|
using System.Net;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using WatchIt.Common.Model;
|
||||||
|
using WatchIt.Common.Model.Accounts;
|
||||||
|
using WatchIt.Common.Model.Photos;
|
||||||
|
using WatchIt.Website.Components.Pages.UserEditPage.Panels;
|
||||||
|
using WatchIt.Website.Layout;
|
||||||
|
using WatchIt.Website.Services.Authentication;
|
||||||
|
using WatchIt.Website.Services.Client.Accounts;
|
||||||
|
|
||||||
namespace WatchIt.Website.Pages;
|
namespace WatchIt.Website.Pages;
|
||||||
|
|
||||||
public partial class UserEditPage : ComponentBase
|
public partial class UserEditPage : ComponentBase
|
||||||
{
|
{
|
||||||
|
#region SERVICES
|
||||||
|
|
||||||
|
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||||
|
[Inject] private IAuthenticationService AuthenticationService { get; set; } = default!;
|
||||||
|
[Inject] private IAccountsClientService AccountsClientService { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PARAMETERS
|
||||||
|
|
||||||
|
[CascadingParameter] public MainLayout Layout { get; set; } = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region FIELDS
|
||||||
|
|
||||||
|
private AccountResponse? _accountData;
|
||||||
|
|
||||||
|
private UserEditPageHeaderPanelComponent _header = default!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region PRIVATE METHODS
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
Layout.BackgroundPhoto = null;
|
||||||
|
|
||||||
|
User? user = await AuthenticationService.GetUserAsync();
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo($"/auth?redirect_to={WebUtility.UrlEncode("/user/edit")}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
await Task.WhenAll(
|
||||||
|
[
|
||||||
|
AccountsClientService.GetAccountInfo(user.Id, data => _accountData = data),
|
||||||
|
AccountsClientService.GetAccountProfileBackground(user.Id, data => Layout.BackgroundPhoto = data)
|
||||||
|
]);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PictureChanged() => await Task.WhenAll(
|
||||||
|
[
|
||||||
|
_header.ReloadPicture(),
|
||||||
|
Layout.ReloadProfilePicture()
|
||||||
|
]);
|
||||||
|
|
||||||
|
private void BackgroundChanged(PhotoResponse? background)
|
||||||
|
{
|
||||||
|
Layout.BackgroundPhoto = background;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
{
|
{
|
||||||
<div class="row mt-header">
|
<div class="row mt-header">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<UserPageHeaderPanelComponent AccountData="@(_accountData)"/>
|
<UserPageHeaderPanelComponent AccountProfileInfoData="@(_accountData)"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-over-panel-menu">
|
<div class="row mt-over-panel-menu">
|
||||||
@@ -69,30 +69,33 @@
|
|||||||
<HorizontalListPanelComponent TItem="MovieRatedResponse"
|
<HorizontalListPanelComponent TItem="MovieRatedResponse"
|
||||||
Title="Recently rated movies"
|
Title="Recently rated movies"
|
||||||
Count="6"
|
Count="6"
|
||||||
GetItemsAction="@(action => AccountsClientService.GetAccountRatedMovies(Id!.Value, new MovieRatedQueryParameters { First = 6, OrderBy = "user_rating_date" }, successAction: action))"
|
GetItemsAction="@(action => AccountsClientService.GetAccountRatedMovies(_accountData.Id, new MovieRatedQueryParameters { First = 6, OrderBy = "user_rating_date" }, successAction: action))"
|
||||||
ItemUrlFormatString="/media/{0}"
|
ItemUrlFormatString="/media/{0}"
|
||||||
IdSource="@(item => item.Id)"
|
IdSource="@(item => item.Id)"
|
||||||
NameSource="@(item => item.ReleaseDate.HasValue ? $"{item.Title} ({item.ReleaseDate.Value.Year})" : item.Title)"
|
NameSource="@(item => item.ReleaseDate.HasValue ? $"{item.Title} ({item.ReleaseDate.Value.Year})" : item.Title)"
|
||||||
PosterPlaceholder="/assets/media_poster.png"
|
PosterPlaceholder="/assets/media_poster.png"
|
||||||
GetPictureAction="@((id, action) => MediaClientService.GetMediaPoster(id, action))"/>
|
GetPictureAction="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
|
||||||
|
HidePlace="@(true)"/>
|
||||||
<HorizontalListPanelComponent TItem="SeriesRatedResponse"
|
<HorizontalListPanelComponent TItem="SeriesRatedResponse"
|
||||||
Title="Recently rated TV series"
|
Title="Recently rated TV series"
|
||||||
Count="6"
|
Count="6"
|
||||||
GetItemsAction="@(action => AccountsClientService.GetAccountRatedSeries(Id!.Value, new SeriesRatedQueryParameters { First = 6, OrderBy = "user_rating_date" }, successAction: action))"
|
GetItemsAction="@(action => AccountsClientService.GetAccountRatedSeries(_accountData.Id, new SeriesRatedQueryParameters { First = 6, OrderBy = "user_rating_date" }, successAction: action))"
|
||||||
ItemUrlFormatString="/media/{0}"
|
ItemUrlFormatString="/media/{0}"
|
||||||
IdSource="@(item => item.Id)"
|
IdSource="@(item => item.Id)"
|
||||||
NameSource="@(item => item.ReleaseDate.HasValue ? $"{item.Title} ({item.ReleaseDate.Value.Year})" : item.Title)"
|
NameSource="@(item => item.ReleaseDate.HasValue ? $"{item.Title} ({item.ReleaseDate.Value.Year})" : item.Title)"
|
||||||
PosterPlaceholder="/assets/media_poster.png"
|
PosterPlaceholder="/assets/media_poster.png"
|
||||||
GetPictureAction="@((id, action) => MediaClientService.GetMediaPoster(id, action))"/>
|
GetPictureAction="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
|
||||||
|
HidePlace="@(true)"/>
|
||||||
<HorizontalListPanelComponent TItem="PersonRatedResponse"
|
<HorizontalListPanelComponent TItem="PersonRatedResponse"
|
||||||
Title="Recently rated people"
|
Title="Recently rated people"
|
||||||
Count="6"
|
Count="6"
|
||||||
GetItemsAction="@(action => AccountsClientService.GetAccountRatedPersons(Id!.Value, new PersonRatedQueryParameters { First = 6, OrderBy = "user_rating_last_date" }, successAction: action))"
|
GetItemsAction="@(action => AccountsClientService.GetAccountRatedPersons(_accountData.Id, new PersonRatedQueryParameters { First = 6, OrderBy = "user_rating_last_date" }, successAction: action))"
|
||||||
ItemUrlFormatString="/person/{0}"
|
ItemUrlFormatString="/person/{0}"
|
||||||
IdSource="@(item => item.Id)"
|
IdSource="@(item => item.Id)"
|
||||||
NameSource="@(item => item.Name)"
|
NameSource="@(item => item.Name)"
|
||||||
PosterPlaceholder="/assets/person_poster.png"
|
PosterPlaceholder="/assets/person_poster.png"
|
||||||
GetPictureAction="@((id, action) => PersonsClientService.GetPersonPhoto(id, action))"/>
|
GetPictureAction="@((id, action) => PersonsClientService.GetPersonPhoto(id, action))"
|
||||||
|
HidePlace="@(true)"/>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="movies">
|
<TabPanel Name="movies">
|
||||||
@@ -108,7 +111,7 @@
|
|||||||
SecondaryRatingTitle="User rating"
|
SecondaryRatingTitle="User rating"
|
||||||
UrlIdTemplate="/media/{0}"
|
UrlIdTemplate="/media/{0}"
|
||||||
PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
|
PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
|
||||||
ItemDownloadingTask="@((query, action) => AccountsClientService.GetAccountRatedMovies(Id!.Value, query, action))"
|
ItemDownloadingTask="@((query, action) => AccountsClientService.GetAccountRatedMovies(_accountData.Id, query, action))"
|
||||||
SortingOptions="@(new Dictionary<string, string> { { "user_rating", "User rating" }, { "user_rating_date", "User rating date" }, { "rating.count", "Number of ratings" }, { "rating.average", "Average rating" }, { "title", "Title" }, { "release_date", "Release date" } })"
|
SortingOptions="@(new Dictionary<string, string> { { "user_rating", "User rating" }, { "user_rating_date", "User rating date" }, { "rating.count", "Number of ratings" }, { "rating.average", "Average rating" }, { "title", "Title" }, { "release_date", "Release date" } })"
|
||||||
PosterPlaceholder="/assets/media_poster.png"
|
PosterPlaceholder="/assets/media_poster.png"
|
||||||
GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))"
|
GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))"
|
||||||
@@ -132,7 +135,7 @@
|
|||||||
SecondaryRatingTitle="User rating"
|
SecondaryRatingTitle="User rating"
|
||||||
UrlIdTemplate="/media/{0}"
|
UrlIdTemplate="/media/{0}"
|
||||||
PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
|
PictureDownloadingTask="@((id, action) => MediaClientService.GetMediaPoster(id, action))"
|
||||||
ItemDownloadingTask="@((query, action) => AccountsClientService.GetAccountRatedSeries(Id!.Value, query, action))"
|
ItemDownloadingTask="@((query, action) => AccountsClientService.GetAccountRatedSeries(_accountData.Id, query, action))"
|
||||||
SortingOptions="@(new Dictionary<string, string> { { "user_rating", "User rating" }, { "user_rating_date", "User rating date" }, { "rating.count", "Number of ratings" }, { "rating.average", "Average rating" }, { "title", "Title" }, { "release_date", "Release date" } })"
|
SortingOptions="@(new Dictionary<string, string> { { "user_rating", "User rating" }, { "user_rating_date", "User rating date" }, { "rating.count", "Number of ratings" }, { "rating.average", "Average rating" }, { "title", "Title" }, { "release_date", "Release date" } })"
|
||||||
PosterPlaceholder="/assets/media_poster.png"
|
PosterPlaceholder="/assets/media_poster.png"
|
||||||
GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))"
|
GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))"
|
||||||
@@ -155,7 +158,7 @@
|
|||||||
SecondaryRatingTitle="User rating"
|
SecondaryRatingTitle="User rating"
|
||||||
UrlIdTemplate="/media/{0}"
|
UrlIdTemplate="/media/{0}"
|
||||||
PictureDownloadingTask="@((id, action) => PersonsClientService.GetPersonPhoto(id, action))"
|
PictureDownloadingTask="@((id, action) => PersonsClientService.GetPersonPhoto(id, action))"
|
||||||
ItemDownloadingTask="@((query, action) => AccountsClientService.GetAccountRatedPersons(Id!.Value, query, action))"
|
ItemDownloadingTask="@((query, action) => AccountsClientService.GetAccountRatedPersons(_accountData.Id, query, action))"
|
||||||
SortingOptions="@(new Dictionary<string, string>
|
SortingOptions="@(new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{ "user_rating.average", "Average user rating" },
|
{ "user_rating.average", "Average user rating" },
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Net;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using WatchIt.Common.Model.Accounts;
|
using WatchIt.Common.Model.Accounts;
|
||||||
using WatchIt.Website.Layout;
|
using WatchIt.Website.Layout;
|
||||||
@@ -49,17 +50,25 @@ public partial class UserPage : ComponentBase
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
|
List<Task> step1Tasks = new List<Task>();
|
||||||
List<Task> endTasks = new List<Task>();
|
List<Task> endTasks = new List<Task>();
|
||||||
|
|
||||||
// INIT
|
// INIT
|
||||||
Layout.BackgroundPhoto = null;
|
Layout.BackgroundPhoto = null;
|
||||||
|
|
||||||
// STEP 0
|
// STEP 0
|
||||||
endTasks.AddRange(
|
step1Tasks.AddRange(
|
||||||
[
|
[
|
||||||
GetUserData()
|
GetUserData()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// STEP 1
|
||||||
|
await Task.WhenAll(step1Tasks);
|
||||||
|
endTasks.AddRange(
|
||||||
|
[
|
||||||
|
AccountsClientService.GetAccountProfileBackground(_accountData.Id, data => Layout.BackgroundPhoto = data)
|
||||||
|
]);
|
||||||
|
|
||||||
// END
|
// END
|
||||||
await Task.WhenAll(endTasks);
|
await Task.WhenAll(endTasks);
|
||||||
|
|
||||||
@@ -75,7 +84,7 @@ public partial class UserPage : ComponentBase
|
|||||||
{
|
{
|
||||||
if (user is null)
|
if (user is null)
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo("/auth");
|
NavigationManager.NavigateTo($"/auth?redirect_to={WebUtility.UrlEncode("/user")}");
|
||||||
_redirection = true;
|
_redirection = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,19 @@
|
|||||||
"Base": "/accounts",
|
"Base": "/accounts",
|
||||||
"Register": "/register",
|
"Register": "/register",
|
||||||
"Authenticate": "/authenticate",
|
"Authenticate": "/authenticate",
|
||||||
"AuthenticateRefresh": "/authenticate-refresh",
|
"AuthenticateRefresh": "/authenticate_refresh",
|
||||||
"Logout": "/logout",
|
"Logout": "/logout",
|
||||||
"GetProfilePicture": "/{0}/profile-picture",
|
"GetAccountProfilePicture": "/{0}/profile_picture",
|
||||||
|
"PutAccountProfilePicture": "/profile_picture",
|
||||||
|
"DeleteAccountProfilePicture": "/profile_picture",
|
||||||
|
"GetAccountProfileBackground": "/{0}/profile_background",
|
||||||
|
"PutAccountProfileBackground": "/profile_background",
|
||||||
|
"DeleteAccountProfileBackground": "/profile_background",
|
||||||
"GetAccountInfo": "/{0}/info",
|
"GetAccountInfo": "/{0}/info",
|
||||||
"PutAccountInfo": "/info",
|
"PutAccountProfileInfo": "/profile_info",
|
||||||
|
"PatchAccountUsername": "/username",
|
||||||
|
"PatchAccountEmail": "/email",
|
||||||
|
"PatchAccountPassword": "/password",
|
||||||
"GetAccountRatedMovies": "/{0}/movies",
|
"GetAccountRatedMovies": "/{0}/movies",
|
||||||
"GetAccountRatedSeries": "/{0}/series",
|
"GetAccountRatedSeries": "/{0}/series",
|
||||||
"GetAccountRatedPersons": "/{0}/persons"
|
"GetAccountRatedPersons": "/{0}/persons"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1rem 1.5rem !important;
|
padding: 1rem 1.5rem !important;
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-menu > li > a {
|
.panel-menu > li > a {
|
||||||
@@ -52,6 +52,14 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* SECTION HEADER */
|
||||||
|
|
||||||
|
.panel-section-header {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* BACKGROUNDS */
|
/* BACKGROUNDS */
|
||||||
|
|
||||||
.panel-background-gold {
|
.panel-background-gold {
|
||||||
|
|||||||
50
WatchIt.slnx
50
WatchIt.slnx
@@ -1,50 +0,0 @@
|
|||||||
<Solution>
|
|
||||||
<Folder Name="/WatchIt.Common/">
|
|
||||||
<Project Path="WatchIt.Common\WatchIt.Common.Model\WatchIt.Common.Model.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.Common\WatchIt.Common.Query\WatchIt.Common.Query.csproj" Type="Classic C#" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/WatchIt.Common/WatchIt.Common.Services/">
|
|
||||||
<Project Path="WatchIt.Common\WatchIt.Common.Services\WatchIt.Common.Services.HttpClient\WatchIt.Common.Services.HttpClient.csproj" Type="Classic C#" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/WatchIt.Database/">
|
|
||||||
<Project Path="WatchIt.Database\WatchIt.Database\WatchIt.Database.csproj" Type="Classic C#" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/WatchIt.Database/WatchIt.Database.Model/">
|
|
||||||
<Project Path="WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model.Configuration\WatchIt.Database.Model.Configuration.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model.Seeding\WatchIt.Database.Model.Seeding.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.Database\WatchIt.Database.Model\WatchIt.Database.Model\WatchIt.Database.Model.csproj" Type="Classic C#" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/WatchIt.WebAPI/">
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Controllers\WatchIt.WebAPI.Controllers.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Validators\WatchIt.WebAPI.Validators.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.WorkerServices\WatchIt.WebAPI.WorkerServices.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI\WatchIt.WebAPI.csproj" Type="Classic C#" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/WatchIt.WebAPI/WatchIt.WebAPI.Services/" />
|
|
||||||
<Folder Name="/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/">
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Accounts\WatchIt.WebAPI.Services.Controllers.Accounts.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Common\WatchIt.WebAPI.Services.Controllers.Common.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Genders\WatchIt.WebAPI.Services.Controllers.Genders.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Genres\WatchIt.WebAPI.Services.Controllers.Genres.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Media\WatchIt.WebAPI.Services.Controllers.Media.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Movies\WatchIt.WebAPI.Services.Controllers.Movies.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Persons\WatchIt.WebAPI.Services.Controllers.Persons.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Photos\WatchIt.WebAPI.Services.Controllers.Photos.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Roles\WatchIt.WebAPI.Services.Controllers.Roles.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Series\WatchIt.WebAPI.Services.Controllers.Series.csproj" Type="Classic C#" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/">
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Configuration\WatchIt.WebAPI.Services.Utility.Configuration.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.Tokens\WatchIt.WebAPI.Services.Utility.Tokens.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Utility\WatchIt.WebAPI.Services.Utility.User\WatchIt.WebAPI.Services.Utility.User.csproj" Type="Classic C#" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/WatchIt.Website/">
|
|
||||||
<Project Path="WatchIt.Website\WatchIt.Website\WatchIt.Website.csproj" Type="Classic C#" />
|
|
||||||
</Folder>
|
|
||||||
<Folder Name="/WatchIt.Website/WatchIt.Website.Services/">
|
|
||||||
<Project Path="WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.Authentication\WatchIt.Website.Services.Authentication.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.Client\WatchIt.Website.Services.Client.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.Configuration\WatchIt.Website.Services.Configuration.csproj" Type="Classic C#" />
|
|
||||||
<Project Path="WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.Tokens\WatchIt.Website.Services.Tokens.csproj" Type="Classic C#" />
|
|
||||||
</Folder>
|
|
||||||
</Solution>
|
|
||||||
Reference in New Issue
Block a user