diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountEmailRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountEmailRequest.cs new file mode 100644 index 0000000..bdf5bab --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountEmailRequest.cs @@ -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 +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountPasswordRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountPasswordRequest.cs new file mode 100644 index 0000000..e0048c1 --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountPasswordRequest.cs @@ -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 +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfileBackgroundRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfileBackgroundRequest.cs new file mode 100644 index 0000000..6f8ce7b --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfileBackgroundRequest.cs @@ -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 +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfileInfoRequest.cs similarity index 50% rename from WatchIt.Common/WatchIt.Common.Model/Accounts/AccountRequest.cs rename to WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfileInfoRequest.cs index 30b1d7b..e26f615 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountRequest.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfileInfoRequest.cs @@ -2,9 +2,12 @@ using System.Text.Json.Serialization; namespace WatchIt.Common.Model.Accounts; -public class AccountRequest : Account +public class AccountProfileInfoRequest { #region PROPERTIES + + [JsonPropertyName("description")] + public string? Description { get; set; } [JsonPropertyName("gender_id")] 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 public void UpdateAccount(Database.Model.Account.Account account) { - account.Username = Username; - account.Email = Email; account.Description = Description; account.GenderId = GenderId; } diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfilePictureRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfilePictureRequest.cs new file mode 100644 index 0000000..1fbea18 --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountProfilePictureRequest.cs @@ -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; + } +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountUsernameRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountUsernameRequest.cs new file mode 100644 index 0000000..5219594 --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountUsernameRequest.cs @@ -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 +} \ No newline at end of file diff --git a/WatchIt.Database/WatchIt.Database.Model/WatchIt.Database.Model/Account/Account.cs b/WatchIt.Database/WatchIt.Database.Model/WatchIt.Database.Model/Account/Account.cs index c24a5ec..f7f5db4 100644 --- a/WatchIt.Database/WatchIt.Database.Model/WatchIt.Database.Model/Account/Account.cs +++ b/WatchIt.Database/WatchIt.Database.Model/WatchIt.Database.Model/Account/Account.cs @@ -15,9 +15,9 @@ public class Account public short? GenderId { get; set; } public Guid? ProfilePictureId { get; set; } public Guid? BackgroundPictureId { get; set; } - public required byte[] Password { get; set; } - public required string LeftSalt { get; set; } - public required string RightSalt { get; set; } + public byte[] Password { get; set; } + public string LeftSalt { get; set; } + public string RightSalt { get; set; } public bool IsAdmin { get; set; } = false; public DateTime CreationDate { get; set; } public DateTime LastActive { get; set; } diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs index 3bcb30d..56ea67a 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Movies; using WatchIt.Common.Model.Persons; +using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Series; using WatchIt.WebAPI.Services.Controllers.Accounts; @@ -14,6 +15,8 @@ namespace WatchIt.WebAPI.Controllers; [Route("accounts")] public class AccountsController(IAccountsControllerService accountsControllerService) : ControllerBase { + #region Basic + [HttpPost("register")] [AllowAnonymous] [ProducesResponseType(typeof(RegisterResponse), StatusCodes.Status201Created)] @@ -27,7 +30,7 @@ public class AccountsController(IAccountsControllerService accountsControllerSer [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] public async Task Authenticate([FromBody]AuthenticateRequest body) => await accountsControllerService.Authenticate(body); - [HttpPost("authenticate-refresh")] + [HttpPost("authenticate_refresh")] [Authorize(AuthenticationSchemes = "refresh")] [ProducesResponseType(typeof(AuthenticateResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] @@ -39,25 +42,93 @@ public class AccountsController(IAccountsControllerService accountsControllerSer [ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)] public async Task Logout() => await accountsControllerService.Logout(); - [HttpGet("{id}/profile-picture")] + #endregion + + #region Profile picture + + [HttpGet("{id}/profile_picture")] [AllowAnonymous] [ProducesResponseType(typeof(AccountProfilePictureResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task 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 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 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 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 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 DeleteAccountProfileBackground() => await accountsControllerService.DeleteAccountProfileBackground(); + + #endregion + + #region Info + [HttpGet("{id}/info")] [AllowAnonymous] [ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetAccountInfo([FromRoute]long id) => await accountsControllerService.GetAccountInfo(id); - [HttpPut("info")] + [HttpPut("profile_info")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PutAccountInfo([FromBody]AccountRequest data) => await accountsControllerService.PutAccountInfo(data); + public async Task 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 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 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 PatchAccountPassword([FromBody]AccountPasswordRequest data) => await accountsControllerService.PatchAccountPassword(data); + + #endregion [HttpGet("{id}/movies")] [AllowAnonymous] diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/AccountsControllerService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/AccountsControllerService.cs index a4151d7..cb6a052 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/AccountsControllerService.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/AccountsControllerService.cs @@ -8,6 +8,7 @@ using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Media; using WatchIt.Common.Model.Movies; using WatchIt.Common.Model.Persons; +using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Series; using WatchIt.Database; using WatchIt.Database.Model.Account; @@ -32,20 +33,17 @@ public class AccountsControllerService( { #region PUBLIC METHODS + #region Basic + public async Task Register(RegisterRequest data) { - string leftSalt = StringExtensions.CreateRandom(20); - string rightSalt = StringExtensions.CreateRandom(20); - byte[] hash = ComputeHash(data.Password, leftSalt, rightSalt); - Account account = new Account { Username = data.Username, Email = data.Email, - Password = hash, - LeftSalt = leftSalt, - RightSalt = rightSalt, }; + + SetPassword(account, data.Password); await database.Accounts.AddAsync(account); await database.SaveChangesAsync(); @@ -125,6 +123,10 @@ public class AccountsControllerService( return RequestResult.NoContent(); } + #endregion + + #region Profile picture + public async Task GetAccountProfilePicture(long id) { Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id); @@ -142,7 +144,100 @@ public class AccountsControllerService( AccountProfilePictureResponse picture = new AccountProfilePictureResponse(account.ProfilePicture); return RequestResult.Ok(picture); } + + public async Task 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 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 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 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 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 GetAccountInfo(long id) { Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id); @@ -151,11 +246,11 @@ public class AccountsControllerService( return RequestResult.NotFound(); } - AccountResponse response = new AccountResponse(account); - return RequestResult.Ok(response); + AccountResponse profileInfoResponse = new AccountResponse(account); + return RequestResult.Ok(profileInfoResponse); } - public async Task PutAccountInfo(AccountRequest data) + public async Task PutAccountProfileInfo(AccountProfileInfoRequest data) { Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == userService.GetUserId()); if (account is null) @@ -164,8 +259,57 @@ public class AccountsControllerService( } data.UpdateAccount(account); + await database.SaveChangesAsync(); + return RequestResult.Ok(); } + + public async Task 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 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 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 GetAccountRatedMovies(long id, MovieRatedQueryParameters query) { @@ -219,5 +363,16 @@ public class AccountsControllerService( 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 } \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/IAccountsControllerService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/IAccountsControllerService.cs index 500b256..0728d3e 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/IAccountsControllerService.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Accounts/IAccountsControllerService.cs @@ -14,8 +14,16 @@ public interface IAccountsControllerService Task AuthenticateRefresh(); Task Logout(); Task GetAccountProfilePicture(long id); + Task PutAccountProfilePicture(AccountProfilePictureRequest data); + Task DeleteAccountProfilePicture(); + Task GetAccountProfileBackground(long id); + Task PutAccountProfileBackground(AccountProfileBackgroundRequest data); + Task DeleteAccountProfileBackground(); Task GetAccountInfo(long id); - Task PutAccountInfo(AccountRequest data); + Task PutAccountProfileInfo(AccountProfileInfoRequest data); + Task PatchAccountUsername(AccountUsernameRequest data); + Task PatchAccountEmail(AccountEmailRequest data); + Task PatchAccountPassword(AccountPasswordRequest data); Task GetAccountRatedMovies(long id, MovieRatedQueryParameters query); Task GetAccountRatedSeries(long id, SeriesRatedQueryParameters query); Task GetAccountRatedPersons(long id, PersonRatedQueryParameters query); diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountEmailRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountEmailRequestValidator.cs new file mode 100644 index 0000000..6bc7e63 --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountEmailRequestValidator.cs @@ -0,0 +1,15 @@ +using FluentValidation; +using WatchIt.Common.Model.Accounts; +using WatchIt.Database; + +namespace WatchIt.WebAPI.Validators.Accounts; + +public class AccountEmailRequestValidator : AbstractValidator +{ + public AccountEmailRequestValidator(DatabaseContext database) + { + RuleFor(x => x.NewEmail).EmailAddress() + .CannotBeIn(database.Accounts, x => x.Email) + .WithMessage("Email was already used"); + } +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountPasswordRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountPasswordRequestValidator.cs new file mode 100644 index 0000000..ffa2096 --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountPasswordRequestValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; +using WatchIt.Common.Model.Accounts; +using WatchIt.Database; + +namespace WatchIt.WebAPI.Validators.Accounts; + +public class AccountPasswordRequestValidator : AbstractValidator +{ + 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); + } +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfileBackgroundRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfileBackgroundRequestValidator.cs new file mode 100644 index 0000000..205be53 --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfileBackgroundRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using WatchIt.Common.Model.Accounts; +using WatchIt.Database; + +namespace WatchIt.WebAPI.Validators.Accounts; + +public class AccountProfileBackgroundRequestValidator : AbstractValidator +{ + 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"); + } +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfileInfoRequestValidator.cs similarity index 52% rename from WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountRequestValidator.cs rename to WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfileInfoRequestValidator.cs index a5b3265..aa20074 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountRequestValidator.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfileInfoRequestValidator.cs @@ -4,14 +4,10 @@ using WatchIt.Database; namespace WatchIt.WebAPI.Validators.Accounts; -public class AccountRequestValidator : AbstractValidator +public class AccountProfileInfoRequestValidator : AbstractValidator { - 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); When(x => x.GenderId.HasValue, () => { diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfilePictureRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfilePictureRequestValidator.cs new file mode 100644 index 0000000..873766c --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountProfilePictureRequestValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using WatchIt.Common.Model.Accounts; + +namespace WatchIt.WebAPI.Validators.Accounts; + +public class AccountProfilePictureRequestValidator : AbstractValidator +{ + public AccountProfilePictureRequestValidator() + { + RuleFor(x => x.Image).NotEmpty(); + RuleFor(x => x.MimeType).Matches(@"\w+/.+").WithMessage("Incorrect mimetype"); + } +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountUsernameRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountUsernameRequestValidator.cs new file mode 100644 index 0000000..6c0ad30 --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountUsernameRequestValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; +using WatchIt.Common.Model.Accounts; +using WatchIt.Database; + +namespace WatchIt.WebAPI.Validators.Accounts; + +public class AccountUsernameRequestValidator : AbstractValidator +{ + public AccountUsernameRequestValidator(DatabaseContext database) + { + RuleFor(x => x.NewUsername).MinimumLength(5) + .MaximumLength(50) + .CannotBeIn(database.Accounts, x => x.Username) + .WithMessage("Username is already used"); + } +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/AccountsClientService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/AccountsClientService.cs index c246c03..49e0344 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/AccountsClientService.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/AccountsClientService.cs @@ -1,6 +1,7 @@ using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Movies; using WatchIt.Common.Model.Persons; +using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Series; using WatchIt.Common.Services.HttpClient; using WatchIt.Website.Services.Configuration; @@ -71,7 +72,7 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig public async Task GetAccountProfilePicture(long id, Action? successAction = null, Action>? 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); HttpResponse response = await httpClientService.SendRequestAsync(request); @@ -80,6 +81,74 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig .RegisterActionFor404NotFound(notFoundAction) .ExecuteAction(); } + + public async Task PutAccountProfilePicture(AccountProfilePictureRequest data, Action? successAction = null, Action>? 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? successAction = null, Action>? 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? successAction = null, Action>? 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? successAction = null, Action? notFoundAction = null) { @@ -92,9 +161,9 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig .ExecuteAction(); } - public async Task PutAccountInfo(AccountRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null) + public async Task PutAccountProfileInfo(AccountProfileInfoRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null) { - string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountInfo); + string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountProfileInfo); HttpRequest request = new HttpRequest(HttpMethodType.Put, url) { Body = data, @@ -104,7 +173,51 @@ public class AccountsClientService(IHttpClientService httpClientService, IConfig response.RegisterActionFor2XXSuccess(successAction) .RegisterActionFor400BadRequest(badRequestAction) .RegisterActionFor401Unauthorized(unauthorizedAction) - .RegisterActionFor404NotFound(notFoundAction) + .ExecuteAction(); + } + + public async Task PatchAccountUsername(AccountUsernameRequest data, Action? successAction = null, Action>? 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>? 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>? 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(); } diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/IAccountsClientService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/IAccountsClientService.cs index 6f16640..84b48f5 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/IAccountsClientService.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Client/Accounts/IAccountsClientService.cs @@ -1,6 +1,7 @@ using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Movies; using WatchIt.Common.Model.Persons; +using WatchIt.Common.Model.Photos; using WatchIt.Common.Model.Series; namespace WatchIt.Website.Services.Client.Accounts; @@ -12,8 +13,16 @@ public interface IAccountsClientService Task AuthenticateRefresh(Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null); Task Logout(Action? successAction = null); Task GetAccountProfilePicture(long id, Action? successAction = null, Action>? badRequestAction = null, Action? notFoundAction = null); + Task PutAccountProfilePicture(AccountProfilePictureRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null); + Task DeleteAccountProfilePicture(Action? successAction = null, Action? unauthorizedAction = null); + Task GetAccountProfileBackground(long id, Action? successAction = null, Action>? badRequestAction = null, Action? notFoundAction = null); + Task PutAccountProfileBackground(AccountProfileBackgroundRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null); + Task DeleteAccountProfileBackground(Action? successAction = null, Action? unauthorizedAction = null); Task GetAccountInfo(long id, Action? successAction = null, Action? notFoundAction = null); - Task PutAccountInfo(AccountRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null); + Task PutAccountProfileInfo(AccountProfileInfoRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null); + Task PatchAccountUsername(AccountUsernameRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null); + Task PatchAccountEmail(AccountEmailRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null); + Task PatchAccountPassword(AccountPasswordRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null); Task GetAccountRatedMovies(long id, MovieRatedQueryParameters query, Action>? successAction = null, Action? notFoundAction = null); Task GetAccountRatedSeries(long id, SeriesRatedQueryParameters query, Action>? successAction = null, Action? notFoundAction = null); Task GetAccountRatedPersons(long id, PersonRatedQueryParameters query, Action>? successAction = null, Action? notFoundAction = null); diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Configuration/Model/Accounts.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Configuration/Model/Accounts.cs index 5731d34..fae7f58 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Configuration/Model/Accounts.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Configuration/Model/Accounts.cs @@ -7,9 +7,17 @@ public class Accounts public string Authenticate { get; set; } public string AuthenticateRefresh { 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 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 GetAccountRatedSeries { get; set; } public string GetAccountRatedPersons { get; set; } diff --git a/WatchIt.Website/WatchIt.Website/App.razor b/WatchIt.Website/WatchIt.Website/App.razor index 5ceca05..f460abd 100644 --- a/WatchIt.Website/WatchIt.Website/App.razor +++ b/WatchIt.Website/WatchIt.Website/App.razor @@ -10,10 +10,10 @@ - + - + diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/HorizontalListPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/HorizontalListPanelComponent.razor index a5485a0..f1e5efe 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/HorizontalListPanelComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/HorizontalListPanelComponent.razor @@ -16,7 +16,7 @@ { @{int iCopy = i;} - diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/HorizontalListPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/HorizontalListPanelComponent.razor.cs index 971d659..326590d 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/HorizontalListPanelComponent.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/HorizontalListPanelComponent.razor.cs @@ -17,6 +17,7 @@ public partial class HorizontalListPanelComponent : ComponentBase [Parameter] public required Func NameSource { get; set; } [Parameter] public required string PosterPlaceholder { get; set; } [Parameter] public required Func, Task> GetPictureAction { get; set; } + [Parameter] public bool HidePlace { get; set; } #endregion diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor index 2264955..37613f4 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor @@ -2,7 +2,7 @@ @if (_loaded) {
- + @if (_pictureChanged || _pictureSaved is not null) { diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor.cs index 3bd56b1..1949048 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Panels/PictureEditorPanelComponent.razor.cs @@ -11,10 +11,12 @@ public partial class PictureEditorPanelComponent : ComponentBase [Parameter] public long? Id { get; set; } [Parameter] public int ContentWidth { get; set; } = 300; [Parameter] public required string PicturePlaceholder { get; set; } + [Parameter] public bool Circle { get; set; } [Parameter] public string Class { get; set; } = string.Empty; [Parameter] public required Func, Task> PictureGetTask { get; set; } [Parameter] public required Func, Task> PicturePutTask { get; set; } [Parameter] public required Func PictureDeleteTask { get; set; } + [Parameter] public Action? OnPictureChanged { get; set; } #endregion @@ -92,6 +94,7 @@ public partial class PictureEditorPanelComponent : ComponentBase _pictureSelected = data; _pictureChanged = false; _pictureSaving = false; + OnPictureChanged?.Invoke(data); } _pictureSaving = true; @@ -112,6 +115,7 @@ public partial class PictureEditorPanelComponent : ComponentBase _pictureSelected = null; _pictureChanged = false; _pictureDeleting = false; + OnPictureChanged?.Invoke(null); } _pictureDeleting = true; diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor index ee75eef..6b532d5 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor @@ -1 +1,7 @@ -avatar \ No newline at end of file + \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor.cs index 21e0402..42d51f8 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/AccountPictureComponent.razor.cs @@ -30,6 +30,18 @@ public partial class AccountPictureComponent : ComponentBase private AccountProfilePictureResponse? _picture; #endregion + + + + #region PUBLIC METHODS + + public async Task Reload() + { + await AccountsClientService.GetAccountProfilePicture(Id, data => _picture = data, notFoundAction: () => _picture = null); + StateHasChanged(); + } + + #endregion @@ -39,18 +51,7 @@ public partial class AccountPictureComponent : ComponentBase { if (firstRender) { - List endTasks = new List(); - - // STEP 0 - endTasks.AddRange( - [ - AccountsClientService.GetAccountProfilePicture(Id, data => _picture = data) - ]); - - // END - await Task.WhenAll(endTasks); - - StateHasChanged(); + await Reload(); } } diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/HorizontalListItemComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/HorizontalListItemComponent.razor index 89c1a89..4030e10 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/HorizontalListItemComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/HorizontalListItemComponent.razor @@ -2,11 +2,16 @@
-
-
@(Place)
-
+ @if (Place.HasValue) + { +
+
@(Place)
+
+ }
-
@(Name)
+
+
@(Name)
+
diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/HorizontalListItemComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/HorizontalListItemComponent.razor.cs index f80bcf4..73b4506 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/HorizontalListItemComponent.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/HorizontalListItemComponent.razor.cs @@ -7,7 +7,7 @@ public partial class HorizontalListItemComponent : ComponentBase { #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 PosterPlaceholder { get; set; } [Parameter] public required Func, Task> GetPosterAction { get; set; } diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor index 398c4d6..358a074 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor @@ -1 +1 @@ -@(AlternativeText) \ No newline at end of file +@(AlternativeText) \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor.cs index 213b9c2..08800d4 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Components/Common/Subcomponents/PictureComponent.razor.cs @@ -14,6 +14,8 @@ public partial class PictureComponent : ComponentBase [Parameter] public string Class { get; set; } = string.Empty; [Parameter] public int? Height { get; set; } [Parameter] public int? Width { get; set; } + [Parameter] public bool Circle { get; set; } + [Parameter] public bool Shadow { get; set; } = true; #endregion @@ -40,6 +42,11 @@ public partial class PictureComponent : ComponentBase { _attributes.Add("width", Width.Value); } + + if (Circle) + { + AspectRatio = PictureComponentAspectRatio.Square; + } } #endregion @@ -71,6 +78,7 @@ public partial class PictureComponent : ComponentBase public static readonly PictureComponentAspectRatio Default = new PictureComponentAspectRatio(); public static readonly PictureComponentAspectRatio Photo = new PictureComponentAspectRatio(16, 9); + public static readonly PictureComponentAspectRatio Square = new PictureComponentAspectRatio(1, 1); #endregion diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/AccountEditHeaderPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/AccountEditHeaderPanelComponent.razor new file mode 100644 index 0000000..a051670 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/AccountEditHeaderPanelComponent.razor @@ -0,0 +1,5 @@ +
+
+

Account settings

+
+
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewEmailPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewEmailPanelComponent.razor new file mode 100644 index 0000000..612abe7 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewEmailPanelComponent.razor @@ -0,0 +1,46 @@ +
+
+

Change email

+ @if (_data is not null) + { + + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ @if (!string.IsNullOrWhiteSpace(_error)) + { + @(_error) + } + else if (_saved) + { + New email saved! + } +
+
+ +
+
+
+
+ } + else + { + + } +
+
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewEmailPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewEmailPanelComponent.razor.cs new file mode 100644 index 0000000..77043e6 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewEmailPanelComponent.razor.cs @@ -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 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 +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewPasswordPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewPasswordPanelComponent.razor new file mode 100644 index 0000000..1a869ac --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewPasswordPanelComponent.razor @@ -0,0 +1,45 @@ +
+
+

Change password

+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ @if (!string.IsNullOrWhiteSpace(_error)) + { + @(_error) + } + else if (_saved) + { + New email saved! + } +
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewPasswordPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewPasswordPanelComponent.razor.cs new file mode 100644 index 0000000..7655c8c --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewPasswordPanelComponent.razor.cs @@ -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 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 +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewUsernamePanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewUsernamePanelComponent.razor new file mode 100644 index 0000000..62a6a9b --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewUsernamePanelComponent.razor @@ -0,0 +1,46 @@ +
+
+

Change username

+ @if (_data is not null) + { + + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ @if (!string.IsNullOrWhiteSpace(_error)) + { + @(_error) + } + else if (_saved) + { + New username saved! + } +
+
+ +
+
+
+
+ } + else + { + + } +
+
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewUsernamePanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewUsernamePanelComponent.razor.cs new file mode 100644 index 0000000..39eeb7f --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/NewUsernamePanelComponent.razor.cs @@ -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 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 +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileBackgroundEditorPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileBackgroundEditorPanelComponent.razor new file mode 100644 index 0000000..6281e1a --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileBackgroundEditorPanelComponent.razor @@ -0,0 +1,121 @@ +@using Blazorise.Components +@using WatchIt.Common.Model.Photos + + + +
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileBackgroundEditorPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileBackgroundEditorPanelComponent.razor.cs new file mode 100644 index 0000000..594ba75 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileBackgroundEditorPanelComponent.razor.cs @@ -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? OnBackgroundChanged { get; set; } + + #endregion + + + + #region FIELDS + + private bool _loaded; + + private bool _editMode; + private long? _selectedMedia; + private IEnumerable? _mediaPhotos; + private bool _backgroundsLoading; + private bool _saveLoading; + + private bool _removeLoading; + + private IEnumerable _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 +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileBackgroundEditorPanelComponent.razor.css b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileBackgroundEditorPanelComponent.razor.css new file mode 100644 index 0000000..910ae4d --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileBackgroundEditorPanelComponent.razor.css @@ -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; +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor new file mode 100644 index 0000000..b5a326d --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor @@ -0,0 +1,60 @@ +@using WatchIt.Common.Model.Genders + + + +
+ @if (_loaded) + { +
+

Basic profile info

+ + +
+
+ +
+ +
+
+
+ +
+ + + @foreach (GenderResponse gender in _genders) + { + + } + +
+
+
+
+ @if (!string.IsNullOrWhiteSpace(_error)) + { + @(_error) + } +
+
+ +
+
+
+
+
+ } + else + { + + } +
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor.cs new file mode 100644 index 0000000..7407687 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditFormPanelComponent.razor.cs @@ -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 _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 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 +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor new file mode 100644 index 0000000..6385348 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/ProfileEditHeaderPanelComponent.razor @@ -0,0 +1,5 @@ +
+
+

Profile settings

+
+
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor new file mode 100644 index 0000000..f9a9c48 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor @@ -0,0 +1,9 @@ +
+
+ +
+

@(AccountData.Username)

+ User settings +
+
+
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.cs b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.cs new file mode 100644 index 0000000..7700867 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.cs @@ -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 +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.css b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.css new file mode 100644 index 0000000..7044add --- /dev/null +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserEditPage/Panels/UserEditPageHeaderPanelComponent.razor.css @@ -0,0 +1,9 @@ +/* IDS */ + +#username { + margin-top: -8px !important; +} + +#secondaryText { + color: lightgray !important; +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Components/Pages/UserPage/Panels/UserPageHeaderPanelComponent.razor b/WatchIt.Website/WatchIt.Website/Components/Pages/UserPage/Panels/UserPageHeaderPanelComponent.razor index 86e41f1..28059b2 100644 --- a/WatchIt.Website/WatchIt.Website/Components/Pages/UserPage/Panels/UserPageHeaderPanelComponent.razor +++ b/WatchIt.Website/WatchIt.Website/Components/Pages/UserPage/Panels/UserPageHeaderPanelComponent.razor @@ -1,26 +1,26 @@
- +
-

@(AccountData.Username)

+

@(AccountProfileInfoData.Username)

- @if (!string.IsNullOrWhiteSpace(AccountData.Description)) + @if (!string.IsNullOrWhiteSpace(AccountProfileInfoData.Description)) { - - @(AccountData.Description) + + @(AccountProfileInfoData.Description) }
-
+
@Body
diff --git a/WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs b/WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs index 10a09d8..00657c8 100644 --- a/WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Layout/MainLayout.razor.cs @@ -2,6 +2,7 @@ using System.Net; using Microsoft.AspNetCore.Components; using WatchIt.Common.Model.Accounts; using WatchIt.Common.Model.Photos; +using WatchIt.Website.Components.Common.Subcomponents; using WatchIt.Website.Services.Authentication; using WatchIt.Website.Services.Tokens; using WatchIt.Website.Services.Client.Accounts; @@ -20,6 +21,7 @@ public partial class MainLayout : LayoutComponentBase [Inject] public IAuthenticationService AuthenticationService { get; set; } = default!; [Inject] public IMediaClientService MediaClientService { get; set; } = default!; [Inject] public IPhotosClientService PhotosClientService { get; set; } = default!; + [Inject] public IAccountsClientService AccountsClientService { get; set; } = default!; #endregion @@ -27,9 +29,12 @@ public partial class MainLayout : LayoutComponentBase #region FIELDS + private AccountPictureComponent? _profilePicture; + private bool _loaded; private User? _user; + private AccountResponse? _accountData; private PhotoResponse? _defaultBackgroundPhoto; private bool _searchbarVisible; @@ -53,6 +58,20 @@ public partial class MainLayout : LayoutComponentBase } #endregion + + + + #region PUBLIC METHODS + + public async Task ReloadProfilePicture() + { + if (_profilePicture is not null) + { + await _profilePicture.Reload(); + } + } + + #endregion @@ -64,17 +83,16 @@ public partial class MainLayout : LayoutComponentBase { if (firstRender) { - List endTasks = new List(); - - // STEP 0 - endTasks.AddRange( + await Task.WhenAll( [ Task.Run(async () => _user = await AuthenticationService.GetUserAsync()), PhotosClientService.GetPhotoRandomBackground(data => _defaultBackgroundPhoto = data) ]); - - // END - await Task.WhenAll(endTasks); + + if (_user is not null) + { + await AccountsClientService.GetAccountInfo(_user.Id, data => _accountData = data); + } _loaded = true; StateHasChanged(); diff --git a/WatchIt.Website/WatchIt.Website/Pages/MediaEditPage.razor b/WatchIt.Website/WatchIt.Website/Pages/MediaEditPage.razor index 86a3d61..f0aa505 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/MediaEditPage.razor +++ b/WatchIt.Website/WatchIt.Website/Pages/MediaEditPage.razor @@ -51,7 +51,18 @@
-

@(_media is not null ? "Edit" : "Create new") @(_movieRequest is not null ? "movie" : "series")@(_media is not null ? $" \"{_media.Title}\"" : string.Empty)

+
+ @if (_media is not null) + { + +

Edit @(_movieRequest is not null ? "movie" : "series") "@(_media.Title)"

+
+ } + else + { +

Create new @(_movieRequest is not null ? "movie" : "series")

+ } +
diff --git a/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor b/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor index 64a2378..1ef2c1b 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor +++ b/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor @@ -1 +1,93 @@ -@page "/user/edit" \ No newline at end of file +@using System.Text +@using WatchIt.Common.Model +@using WatchIt.Common.Model.Photos +@using WatchIt.Website.Components.Pages.UserEditPage.Panels + + +@page "/user/edit" + +@{ + StringBuilder sb = new StringBuilder(" - WatchIt"); + + if (_accountData is null) sb.Insert(0, "Loading..."); + else sb.Insert(0, "User settings"); + + @(sb.ToString()) +} + + + +
+ @if (_accountData is not null) + { +
+
+ +
+
+
+
+ + + Profile + Account + + + +
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ +
+ + + + +
+
+
+
+
+
+ } + else + { +
+
+
+ +
+
+
+ } +
\ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor.cs b/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor.cs index d5a489f..4b29a60 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Pages/UserEditPage.razor.cs @@ -1,7 +1,80 @@ +using System.Net; 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; 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 } \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor b/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor index 1e1d3cf..722e501 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor +++ b/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor @@ -48,7 +48,7 @@ {
- +
@@ -69,30 +69,33 @@ + GetPictureAction="@((id, action) => MediaClientService.GetMediaPoster(id, action))" + HidePlace="@(true)"/> + GetPictureAction="@((id, action) => MediaClientService.GetMediaPoster(id, action))" + HidePlace="@(true)"/> + GetPictureAction="@((id, action) => PersonsClientService.GetPersonPhoto(id, action))" + HidePlace="@(true)"/>
@@ -108,7 +111,7 @@ SecondaryRatingTitle="User rating" UrlIdTemplate="/media/{0}" 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 { { "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" GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))" @@ -132,7 +135,7 @@ SecondaryRatingTitle="User rating" UrlIdTemplate="/media/{0}" 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 { { "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" GetGlobalRatingMethod="@((id, action) => MediaClientService.GetMediaRating(id, action))" @@ -155,7 +158,7 @@ SecondaryRatingTitle="User rating" UrlIdTemplate="/media/{0}" 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 { { "user_rating.average", "Average user rating" }, diff --git a/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor.cs b/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor.cs index 2a2985c..95087f0 100644 --- a/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor.cs +++ b/WatchIt.Website/WatchIt.Website/Pages/UserPage.razor.cs @@ -1,3 +1,4 @@ +using System.Net; using Microsoft.AspNetCore.Components; using WatchIt.Common.Model.Accounts; using WatchIt.Website.Layout; @@ -49,17 +50,25 @@ public partial class UserPage : ComponentBase { if (firstRender) { + List step1Tasks = new List(); List endTasks = new List(); // INIT Layout.BackgroundPhoto = null; // STEP 0 - endTasks.AddRange( + step1Tasks.AddRange( [ GetUserData() ]); + // STEP 1 + await Task.WhenAll(step1Tasks); + endTasks.AddRange( + [ + AccountsClientService.GetAccountProfileBackground(_accountData.Id, data => Layout.BackgroundPhoto = data) + ]); + // END await Task.WhenAll(endTasks); @@ -75,7 +84,7 @@ public partial class UserPage : ComponentBase { if (user is null) { - NavigationManager.NavigateTo("/auth"); + NavigationManager.NavigateTo($"/auth?redirect_to={WebUtility.UrlEncode("/user")}"); _redirection = true; return; } diff --git a/WatchIt.Website/WatchIt.Website/appsettings.json b/WatchIt.Website/WatchIt.Website/appsettings.json index 9d0355d..943633b 100644 --- a/WatchIt.Website/WatchIt.Website/appsettings.json +++ b/WatchIt.Website/WatchIt.Website/appsettings.json @@ -19,11 +19,19 @@ "Base": "/accounts", "Register": "/register", "Authenticate": "/authenticate", - "AuthenticateRefresh": "/authenticate-refresh", + "AuthenticateRefresh": "/authenticate_refresh", "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", - "PutAccountInfo": "/info", + "PutAccountProfileInfo": "/profile_info", + "PatchAccountUsername": "/username", + "PatchAccountEmail": "/email", + "PatchAccountPassword": "/password", "GetAccountRatedMovies": "/{0}/movies", "GetAccountRatedSeries": "/{0}/series", "GetAccountRatedPersons": "/{0}/persons" diff --git a/WatchIt.Website/WatchIt.Website/wwwroot/css/panel.css b/WatchIt.Website/WatchIt.Website/wwwroot/css/panel.css index 0e200f3..6dcb155 100644 --- a/WatchIt.Website/WatchIt.Website/wwwroot/css/panel.css +++ b/WatchIt.Website/WatchIt.Website/wwwroot/css/panel.css @@ -28,7 +28,7 @@ gap: 1rem; 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 { @@ -52,6 +52,14 @@ +/* SECTION HEADER */ + +.panel-section-header { + padding: 0.75rem 1.5rem; +} + + + /* BACKGROUNDS */ .panel-background-gold { diff --git a/WatchIt.slnx b/WatchIt.slnx deleted file mode 100644 index ed0e24e..0000000 --- a/WatchIt.slnx +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file