diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/Account.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/Account.cs new file mode 100644 index 0000000..e96ec0f --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/Account.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace WatchIt.Common.Model.Accounts; + +public abstract class Account +{ + #region PROPERTIES + + [JsonPropertyName("username")] + public required string Username { get; set; } + + [JsonPropertyName("email")] + public required string Email { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + #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/AccountRequest.cs new file mode 100644 index 0000000..30b1d7b --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountRequest.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace WatchIt.Common.Model.Accounts; + +public class AccountRequest : Account +{ + #region PROPERTIES + + [JsonPropertyName("gender_id")] + public short? GenderId { get; set; } + + #endregion + + + + #region PUBLIC METHODS + + public void UpdateAccount(Database.Model.Account.Account account) + { + account.Username = Username; + account.Email = Email; + account.Description = Description; + account.GenderId = GenderId; + } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountResponse.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountResponse.cs new file mode 100644 index 0000000..1595aa8 --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/AccountResponse.cs @@ -0,0 +1,34 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; +using WatchIt.Common.Model.Genders; + +namespace WatchIt.Common.Model.Accounts; + +public class AccountResponse : Account +{ + #region PROPERTIES + + [JsonPropertyName("id")] + public required long Id { get; set; } + + [JsonPropertyName("gender")] + public GenderResponse? Gender { get; set; } + + #endregion + + + + #region CONSTRUCTORS + + [SetsRequiredMembers] + public AccountResponse(Database.Model.Account.Account account) + { + Id = account.Id; + Username = account.Username; + Email = account.Email; + Description = account.Description; + Gender = account.Gender is not null ? new GenderResponse(account.Gender) : null; + } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Accounts/RegisterResponse.cs b/WatchIt.Common/WatchIt.Common.Model/Accounts/RegisterResponse.cs index 335ed0c..b6d904e 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Accounts/RegisterResponse.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Accounts/RegisterResponse.cs @@ -30,7 +30,7 @@ public class RegisterResponse public RegisterResponse() {} [SetsRequiredMembers] - public RegisterResponse(Account account) + public RegisterResponse(Database.Model.Account.Account account) { Id = account.Id; Username = account.Username; diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs index d1a3de0..c431396 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/AccountsController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using WatchIt.Common.Model.Accounts; @@ -41,4 +42,24 @@ public class AccountsController(IAccountsControllerService accountsControllerSer [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetAccountProfilePicture([FromRoute(Name = "id")]long id) => await accountsControllerService.GetAccountProfilePicture(id); + + [HttpGet("{id}/info")] + [AllowAnonymous] + [ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetAccountInfo([FromRoute]long id) => await accountsControllerService.GetAccountInfo(id); + + [HttpGet("info")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetAccountInfo() => await accountsControllerService.GetAccountInfo(); + + [HttpPut("info")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PutAccountInfo([FromBody]AccountRequest data) => await accountsControllerService.PutAccountInfo(data); } \ No newline at end of file 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 3cbdf70..df37957 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 @@ -11,6 +11,7 @@ using WatchIt.WebAPI.Services.Controllers.Common; using WatchIt.WebAPI.Services.Utility.Tokens; using WatchIt.WebAPI.Services.Utility.Tokens.Exceptions; using WatchIt.WebAPI.Services.Utility.User; +using Account = WatchIt.Database.Model.Account.Account; using AccountProfilePicture = WatchIt.Common.Model.Accounts.AccountProfilePicture; namespace WatchIt.WebAPI.Services.Controllers.Accounts; @@ -129,6 +130,31 @@ public class AccountsControllerService( return RequestResult.Ok(picture); } + public async Task GetAccountInfo() => await GetAccountInfo(userService.GetUserId()); + public async Task GetAccountInfo(long id) + { + Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == id); + if (account is null) + { + return RequestResult.NotFound(); + } + + AccountResponse response = new AccountResponse(account); + return RequestResult.Ok(response); + } + + public async Task PutAccountInfo(AccountRequest data) + { + Account? account = await database.Accounts.FirstOrDefaultAsync(x => x.Id == userService.GetUserId()); + if (account is null) + { + return RequestResult.NotFound(); + } + + data.UpdateAccount(account); + return RequestResult.Ok(); + } + #endregion 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 4d7974d..03098fe 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 @@ -10,4 +10,7 @@ public interface IAccountsControllerService Task AuthenticateRefresh(); Task Logout(); Task GetAccountProfilePicture(long id); + Task GetAccountInfo(); + Task GetAccountInfo(long id); + Task PutAccountInfo(AccountRequest data); } \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/UserValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/UserValidator.cs index b3ae31d..3dc0c41 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/UserValidator.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Utility/WatchIt.WebAPI.Services.Utility.User/UserValidator.cs @@ -1,4 +1,5 @@ -using System.Security.Claims; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; using WatchIt.Database; namespace WatchIt.WebAPI.Services.Utility.User; @@ -53,5 +54,17 @@ public class UserValidator return this; } + public UserValidator MustHaveId(long id) + { + Claim adminClaim = _claimsPrincipal.FindFirst(x => x.Type == JwtRegisteredClaimNames.Sub)!; + if (adminClaim.Value == id.ToString()) + { + IsValid = false; + _validationErrors.Add("User have wrong id"); + } + + return this; + } + #endregion } \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountRequestValidator.cs new file mode 100644 index 0000000..a5b3265 --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Accounts/AccountRequestValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using WatchIt.Common.Model.Accounts; +using WatchIt.Database; + +namespace WatchIt.WebAPI.Validators.Accounts; + +public class AccountRequestValidator : AbstractValidator +{ + public AccountRequestValidator(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, () => + { + RuleFor(x => x.GenderId!.Value).MustBeIn(database.Genders.Select(x => x.Id)); + }); + } +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs b/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs index d6c6797..581c7c6 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs @@ -46,6 +46,7 @@ public static class Program while (!dbContext.Database.CanConnect()) { + app.Logger.LogInformation("Waiting for database..."); Thread.Sleep(1000); } diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Accounts.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Accounts.cs index ba1f2db..c8f50a5 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Accounts.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Accounts.cs @@ -8,4 +8,7 @@ public class Accounts public string AuthenticateRefresh { get; set; } public string Logout { get; set; } public string GetProfilePicture { get; set; } + public string GetAccountInfoById { get; set; } + public string GetAccountInfo { get; set; } + public string PutAccountInfo { get; set; } } \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Accounts/AccountsWebAPIService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Accounts/AccountsWebAPIService.cs index f56757e..5e1c092 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Accounts/AccountsWebAPIService.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Accounts/AccountsWebAPIService.cs @@ -80,6 +80,45 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig .ExecuteAction(); } + public async Task GetAccountInfoById(long id, Action? successAction = null, Action? notFoundAction = null) + { + string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountInfoById, id); + HttpRequest request = new HttpRequest(HttpMethodType.Get, url); + + HttpResponse response = await httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor404NotFound(notFoundAction) + .ExecuteAction(); + } + + public async Task GetAccountInfo(Action? successAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null) + { + string url = GetUrl(EndpointsConfiguration.Accounts.GetAccountInfo); + HttpRequest request = new HttpRequest(HttpMethodType.Get, url); + + HttpResponse response = await httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor401Unauthorized(unauthorizedAction) + .RegisterActionFor404NotFound(notFoundAction) + .ExecuteAction(); + } + + public async Task PutAccountInfo(AccountRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null) + { + string url = GetUrl(EndpointsConfiguration.Accounts.PutAccountInfo); + HttpRequest request = new HttpRequest(HttpMethodType.Put, url) + { + Body = data, + }; + + HttpResponse response = await httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor400BadRequest(badRequestAction) + .RegisterActionFor401Unauthorized(unauthorizedAction) + .RegisterActionFor404NotFound(notFoundAction) + .ExecuteAction(); + } + #endregion diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Accounts/IAccountsWebAPIService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Accounts/IAccountsWebAPIService.cs index a2e0df5..8aed63e 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Accounts/IAccountsWebAPIService.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Accounts/IAccountsWebAPIService.cs @@ -9,4 +9,7 @@ public interface IAccountsWebAPIService 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 GetAccountInfoById(long id, Action? successAction = null, Action? notFoundAction = null); + Task GetAccountInfo(Action? successAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null); + Task PutAccountInfo(AccountRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null); } \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website/appsettings.json b/WatchIt.Website/WatchIt.Website/appsettings.json index 2e65e00..3613545 100644 --- a/WatchIt.Website/WatchIt.Website/appsettings.json +++ b/WatchIt.Website/WatchIt.Website/appsettings.json @@ -21,7 +21,10 @@ "Authenticate": "/authenticate", "AuthenticateRefresh": "/authenticate-refresh", "Logout": "/logout", - "GetProfilePicture": "/{0}/profile-picture" + "GetProfilePicture": "/{0}/profile-picture", + "GetAccountInfoById": "/{0}/info", + "GetAccountInfo": "/info", + "PutAccountInfo": "/info" }, "Genders": { "Base": "/genders",