Merge pull request #147 from mateuszskoczek/features/profile_info_endpoints

Features/profile info endpoints
This commit is contained in:
2024-10-27 20:25:11 +01:00
committed by GitHub
Unverified
14 changed files with 217 additions and 4 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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<ActionResult> 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<ActionResult> 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<ActionResult> GetAccountInfo() => await accountsControllerService.GetAccountInfo();
[HttpPut("info")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ProducesResponseType(typeof(AccountResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> PutAccountInfo([FromBody]AccountRequest data) => await accountsControllerService.PutAccountInfo(data);
}

View File

@@ -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<RequestResult> GetAccountInfo() => await GetAccountInfo(userService.GetUserId());
public async Task<RequestResult> 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<RequestResult> 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

View File

@@ -10,4 +10,7 @@ public interface IAccountsControllerService
Task<RequestResult> AuthenticateRefresh();
Task<RequestResult> Logout();
Task<RequestResult> GetAccountProfilePicture(long id);
Task<RequestResult> GetAccountInfo();
Task<RequestResult> GetAccountInfo(long id);
Task<RequestResult> PutAccountInfo(AccountRequest data);
}

View File

@@ -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
}

View File

@@ -0,0 +1,21 @@
using FluentValidation;
using WatchIt.Common.Model.Accounts;
using WatchIt.Database;
namespace WatchIt.WebAPI.Validators.Accounts;
public class AccountRequestValidator : AbstractValidator<AccountRequest>
{
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));
});
}
}

View File

@@ -46,6 +46,7 @@ public static class Program
while (!dbContext.Database.CanConnect())
{
app.Logger.LogInformation("Waiting for database...");
Thread.Sleep(1000);
}

View File

@@ -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; }
}

View File

@@ -80,6 +80,45 @@ public class AccountsWebAPIService(IHttpClientService httpClientService, IConfig
.ExecuteAction();
}
public async Task GetAccountInfoById(long id, Action<AccountResponse>? 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<AccountResponse>? 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<AccountResponse>? successAction = null, Action<IDictionary<string, string[]>>? 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

View File

@@ -9,4 +9,7 @@ public interface IAccountsWebAPIService
Task AuthenticateRefresh(Action<AuthenticateResponse>? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null);
Task Logout(Action? successAction = null);
Task GetAccountProfilePicture(long id, Action<AccountProfilePictureResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? notFoundAction = null);
Task GetAccountInfoById(long id, Action<AccountResponse>? successAction = null, Action? notFoundAction = null);
Task GetAccountInfo(Action<AccountResponse>? successAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null);
Task PutAccountInfo(AccountRequest data, Action<AccountResponse>? successAction = null, Action<IDictionary<string, string[]>>? badRequestAction = null, Action? unauthorizedAction = null, Action? notFoundAction = null);
}

View File

@@ -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",