chore: deleted all the stuff we don't need here

This commit is contained in:
2025-09-20 23:08:08 +03:00
parent 8ea18f1096
commit 44881818a2
67 changed files with 1 additions and 3139 deletions

View File

@@ -1,486 +0,0 @@
using GamificationService.Models.BasicResponses;
using GamificationService.Models.Database;
using GamificationService.Models.DTO;
using GamificationService.Services.JWT;
using GamificationService.Services.NotificationService;
using GamificationService.Utils;
using GamificationService.Utils.Factory;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace GamificationService.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
#region Services
private readonly ILogger<AuthController> _logger;
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IJwtService _jwtService;
private readonly INotificationService _notificationService;
private readonly MailNotificationsFactory _mailNotificationsFactory;
private readonly PushNotificationsFactory _pushNotificationsFactory;
#endregion
#region Constructor
public AuthController(ILogger<AuthController> logger, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IJwtService jwtService, INotificationService notificationService, MailNotificationsFactory mailNotificationsFactory, PushNotificationsFactory pushNotificationsFactory)
{
_logger = logger;
_userManager = userManager;
_signInManager = signInManager;
_jwtService = jwtService;
_notificationService = notificationService;
_mailNotificationsFactory = mailNotificationsFactory;
_pushNotificationsFactory = pushNotificationsFactory;
}
#endregion
#region Actions
#region Auth
/// <summary>
/// Handles user registration.
/// </summary>
/// <param name="model">The registration model.</param>
/// <returns>A response indicating the result of the registration.</returns>
[HttpPost("register")]
public async Task<IActionResult> Register([FromBody] AuthDTO model)
{
try
{
var user = new ApplicationUser()
{
UserName = model.Username,
Email = model.Email,
TwoFactorEnabled = false,
TwoFactorProviders = new List<TwoFactorProvider>() { TwoFactorProvider.NONE }
};
var result = await _userManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
_logger.LogError("User registration failed: {Errors}", result.Errors);
return BadRequest(new BasicResponse
{
Code = 400,
Message = "User registration failed"
});
}
_logger.LogInformation("User registered successfully: {Username}", model.Username);
return Ok(new BasicResponse
{
Code = 200,
Message = "User created successfully"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred during user registration: {Message}", ex.Message);
return StatusCode(500, new BasicResponse
{
Code = 500,
Message = "An error occurred during user registration"
});
}
}
/// <summary>
/// Handles user login.
/// </summary>
/// <param name="model">The login model.</param>
/// <returns>A response indicating the result of the login.</returns>
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] AuthDTO model)
{
try
{
var user = await _userManager.FindByNameAsync(model.Username);
if (user == null)
{
_logger.LogError("Invalid username or password");
return NotFound(new BasicResponse
{
Code = 404,
Message = "Invalid username or password"
});
}
var result = await _signInManager.PasswordSignInAsync( user, model.Password, false, model.RememberMe);
if (result.Succeeded & !result.RequiresTwoFactor)
{
var refreshToken = await _jwtService.GenerateRefreshTokenAsync(user);
var accessToken = _jwtService.GenerateAccessToken(user);
_logger.LogInformation("User logged in successfully: {Username}", model.Username);
return Ok(new LoginResultResponse()
{
RequiresTwoFactorAuth = false,
Success = true,
Token = new RefreshTokenDTO()
{
AccessToken = accessToken,
RefreshToken = refreshToken.Token
}
});
}
else if(result.RequiresTwoFactor)
{
var providerWithMaxWeight = user.TwoFactorProviders
.OrderByDescending(p => (int)p)
.FirstOrDefault();
if (providerWithMaxWeight == TwoFactorProvider.NONE)
{
_logger.LogInformation("User {Username} does not have any two-factor authentication enabled", model.Username);
return StatusCode(418, new LoginResultResponse()
{
RequiresTwoFactorAuth = false,
Success = true,
TwoFactorProvider = (int)providerWithMaxWeight
});
}
var code = await _userManager.GenerateTwoFactorTokenAsync(user, providerWithMaxWeight.ToString());
await SendNotificationAsync(user, "Two-factor authentication code", code, NotificationInformationType.AUTH,providerWithMaxWeight);
_logger.LogInformation("Two-factor authentication required for user {Username}", model.Username);
return Ok(new LoginResultResponse()
{
RequiresTwoFactorAuth = true,
Success = true
});
}
_logger.LogError("Invalid username or password");
return BadRequest(new BasicResponse
{
Code = 400,
Message = "Invalid username or password"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred during user login: {Message}", ex.Message);
return StatusCode(500, new BasicResponse
{
Code = 500,
Message = "An error occurred during user login"
});
}
}
[HttpPost("logout")]
[Authorize]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return Ok("Logged out successfully");
}
[HttpPost("revoke-token")]
[Authorize]
public async Task<IActionResult> RevokeToken()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound("User not found");
}
await _jwtService.RevokeRefreshTokenAsync(user.Id, HttpContext.Request.Cookies["refresh_token"],GetRemoteIpAddress());
return Ok("Token revoked successfully");
}
#endregion
#region Email
[HttpGet("{username}/init-email-verification")]
public async Task<IActionResult> VerifyEmail(string username)
{
try
{
var user = await _userManager.FindByNameAsync(username);
if (user == null)
{
_logger.LogError("Invalid username or password");
return NotFound(new BasicResponse
{
Code = 404,
Message = "Invalid username or password"
});
}
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
await SendNotificationAsync(user, "Email verification code", code, NotificationInformationType.AUTH, TwoFactorProvider.EMAIL);
_logger.LogInformation("Email verification code sent to user {Username}", username);
return Ok(new BasicResponse()
{
Code = 200,
Message = "Email verification code sent"
});
}
catch (Exception e)
{
_logger.LogError(e, "An error occurred during email verification: {Message}", e.Message);
return StatusCode(500, new BasicResponse
{
Code = 500,
Message = "An error occurred during email verification"
});
}
}
[HttpGet("{username}/verify-email/{code}")]
public async Task<IActionResult> VerifyEmail(string username, string code)
{
try
{
var user = await _userManager.FindByNameAsync(username);
if (user == null)
{
_logger.LogError("Invalid username or password");
return NotFound(new BasicResponse
{
Code = 404,
Message = "Invalid username or password"
});
}
var result = await _userManager.ConfirmEmailAsync(user,code);
if (result.Succeeded)
{
_logger.LogInformation("Email verified for user {Username}", username);
user.EmailConfirmed = true;
await _userManager.UpdateAsync(user);
return Ok(new BasicResponse()
{
Code = 200,
Message = "Email verified"
});
}
return BadRequest(new BasicResponse()
{
Code = 400,
Message = "Email verification failed"
});
}
catch (Exception e)
{
_logger.LogError(e, "An error occurred during email verification: {Message}", e.Message);
return StatusCode(500, new BasicResponse
{
Code = 500,
Message = "An error occurred during email verification"
});
}
}
#endregion
#region 2FA
[HttpPost("get-2fa-code")]
public async Task<IActionResult> GetTwoFactorCode([FromBody] GetTwoFactorDTO model)
{
try
{
var user = await _userManager.FindByNameAsync(model.Username);
if (user == null)
{
_logger.LogError("Invalid username or password");
return NotFound(new BasicResponse
{
Code = 404,
Message = "Invalid username or password"
});
}
var providerWithRequiredWeight = user.TwoFactorProviders
.FirstOrDefault(p => (int)p == model.TwoFactorProvider);
var code = await _userManager.GenerateTwoFactorTokenAsync(user, providerWithRequiredWeight.ToString());
await SendNotificationAsync(user, "Two-factor authentication code", code, NotificationInformationType.AUTH,providerWithRequiredWeight);
_logger.LogInformation("Two-factor authentication code sent to user {Username}", model.Username);
return Ok(new BasicResponse()
{
Code = 200,
Message = "Code sent successfully"
});
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to send code"
});
}
}
[HttpPost("verify-2fa")]
public async Task<IActionResult> VerifyTwoFactorCode([FromBody] TwoFactorDTO model)
{
try
{
if (model.Username != null)
{
var user = await _userManager.FindByNameAsync(model.Username);
var providerWithRequiredWeight = user.TwoFactorProviders
.FirstOrDefault(p => (int)p == model.TwoFactorProvider);
var signInResult = _signInManager.TwoFactorSignInAsync(providerWithRequiredWeight.ToString(),
model.Code, false, model.RememberMe);
if (!signInResult.Result.Succeeded)
{
return BadRequest(new BasicResponse()
{
Code = 400,
Message = "Invalid code"
});
}
var token = _jwtService.GenerateAccessToken(user);
var refreshToken = await _jwtService.GenerateRefreshTokenAsync(user);
_logger.LogInformation("User logged in successfully: {Username}", model.Username);
await SendNotificationAsync(user, "Login successful", "You have successfully logged in", NotificationInformationType.WARNING,TwoFactorProvider.EMAIL);
return Ok( new LoginResultResponse()
{
RequiresTwoFactorAuth = false,
Success = true,
Token = new RefreshTokenDTO()
{
AccessToken = token,
RefreshToken = refreshToken.Token
}
});
}
_logger.LogError("Username can't be empty");
return NotFound(new BasicResponse()
{
Code = 404,
Message = "Username can't be empty"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred during user verification: {Message}", ex.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "An error occurred during user verification"
});
}
}
[HttpPost("enable-2fa")]
[Authorize]
public async Task<IActionResult> EnableTwoFactor([FromBody]EnableTwoFactorDTO model)
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound(new BasicResponse()
{
Code = 404,
Message = "User not found"
});
}
user.TwoFactorProviders.Add((TwoFactorProvider)model.TwoFactorProvider);
user.TwoFactorEnabled = true;
await _userManager.UpdateAsync(user);
var secretKey = await _userManager.GenerateTwoFactorTokenAsync(user, TwoFactorProvider.AUTHENTICATOR.ToString());
_logger.LogInformation("User logged in successfully: {Username}", User);
await SendNotificationAsync(user, "Login successful", "You have successfully logged in", NotificationInformationType.WARNING,(TwoFactorProvider)model.TwoFactorProvider);
return Ok(new BasicResponse()
{
Code = 200,
Message = "Two-factor authentication enabled successfully"
});
}
[HttpPost("disable-2fa")]
[Authorize]
public async Task<IActionResult> DisableTwoFactor([FromBody] DisableTwoFactorDTO model)
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound("User not found");
}
if (!await _userManager.VerifyTwoFactorTokenAsync(user,model.TwoFactorProvider.ToString(),model.Code))
{
return BadRequest("Invalid verification code");
}
user.TwoFactorEnabled = false;
await _userManager.UpdateAsync(user);
return Ok("Two-factor authentication disabled");
}
#endregion
#region Helpers
private async Task SendNotificationAsync(ApplicationUser user,
string title,
string message,
NotificationInformationType notificationInformationType,
TwoFactorProvider provider)
{
try
{
switch (provider)
{
case TwoFactorProvider.EMAIL:
await _notificationService.SendMailNotificationAsync(user, _mailNotificationsFactory.CreateNotification(notificationInformationType, title, message));
break;
case TwoFactorProvider.PHONE:
throw new NotImplementedException();
break;
case TwoFactorProvider.PUSH:
await _notificationService.SendPushNotificationAsync(user, _pushNotificationsFactory.CreateNotification(notificationInformationType, title, message));
break;
case TwoFactorProvider.AUTHENTICATOR:
throw new NotImplementedException();
break;
default:
break;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred during notification: {Message}", ex.Message);
}
}
private string GetRemoteIpAddress()
{
if (HttpContext.Connection.RemoteIpAddress != null)
{
return HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString();
}
return string.Empty;
}
#endregion
#endregion
}

View File

@@ -1,186 +0,0 @@
using GamificationService.Models.BasicResponses;
using GamificationService.Models.DTO;
using GamificationService.Services.Rights;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace GamificationService.Controllers;
[ApiController]
[Route("api/[controller]")]
[Authorize(Policy = "Admin")]
public class RightsController : ControllerBase
{
#region Services
private readonly IRightsService _rightsService;
private readonly ILogger<RightsController> _logger;
#endregion
#region Constructor
public RightsController(IRightsService rightsService, ILogger<RightsController> logger)
{
_rightsService = rightsService;
_logger = logger;
}
#endregion
#region Methods
[HttpGet]
public async Task<IActionResult> GetAllRightsAsync([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10)
{
try
{
var (rights, totalCount) = await _rightsService.GetAllRightsAsync(pageNumber, pageSize);
_logger.LogInformation($"Retrieved {rights.Count} rights");
var response = new GetAllRightsResponse()
{
Rights = rights,
TotalCount = totalCount,
PageNumber = pageNumber,
PageSize = pageSize
};
return Ok(response);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to get rights.",
});
}
}
[HttpGet("{id}")]
public async Task<IActionResult> GetRightByIdAsync(long id)
{
var right = await _rightsService.GetRightByIdAsync(id);
_logger.LogInformation($"Retrieved right with id: {id}");
if (right == null)
{
return NotFound(new BasicResponse()
{
Code = 404,
Message = "Right not found"
});
}
return Ok(right);
}
[HttpPost]
public async Task<IActionResult> CreateRightAsync([FromBody] RightDTO model)
{
try
{
var right = await _rightsService.CreateRightAsync(model.Name, model.Description);
_logger.LogInformation($"Created right: {right}");
return CreatedAtAction(nameof(CreateRightAsync), new { id = right.Id }, right);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to create right.",
});
}
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateRightAsync(long id, [FromBody] RightDTO model)
{
try
{
if (await _rightsService.UpdateRightAsync(id, model.Name, model.Description))
{
_logger.LogInformation($"Updated right: {id}");
return Ok(new BasicResponse()
{
Code = 200,
Message = "Rights updated",
});
}
_logger.LogError($"Unknown with right updating, {id}");
return StatusCode(418,
new BasicResponse()
{
Code = 418,
Message = "Failed to update right."
});
}
catch (KeyNotFoundException)
{
_logger.LogError($"Right not found, {id} ");
return NotFound(new BasicResponse()
{
Code = 404,
Message = "Right not found"
});
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to update right"
});
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteRightAsync(long id)
{
try
{
if( await _rightsService.DeleteRightAsync(id))
{
_logger.LogInformation($"Deleted right: {id}");
return Ok(new BasicResponse()
{
Code = 200,
Message = "Rights deleted",
});
}
_logger.LogError($"Unknown error with right deleting, {id} ");
return StatusCode(418, new BasicResponse()
{
Code = 418,
Message = "Failed to delete right"
});
}
catch (KeyNotFoundException)
{
_logger.LogError($"Role not found, {id} ");
return NotFound(new BasicResponse()
{
Code = 404,
Message = "Right not found"
});
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to delete right"
});
}
}
#endregion
}

View File

@@ -1,283 +0,0 @@
using GamificationService.Models.BasicResponses;
using GamificationService.Models.DTO;
using GamificationService.Services.Roles;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
namespace GamificationService.Controllers;
[ApiController]
[Route("api/[controller]")]
[Authorize(Policy = "Admin")]
public class RoleController : ControllerBase
{
#region Services
private readonly IRolesService _rolesService;
private readonly ILogger<RoleController> _logger;
#endregion
#region Constructor
public RoleController(ILogger<RoleController> logger, IRolesService rolesService)
{
_logger = logger;
_rolesService = rolesService;
}
#endregion
#region ControllerMethods
[HttpGet]
public async Task<IActionResult> GetAllRolesAsync([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 10)
{
try
{
var (roles, totalCount) = await _rolesService.GetAllRolesAsync(pageNumber, pageSize);
_logger.LogInformation($"Roles found successfully, {roles.Count}");
var response = new GetAllRolesResponse()
{
Roles = roles,
TotalCount = totalCount,
PageNumber = pageNumber,
PageSize = pageSize
};
return Ok(response);
}
catch (Exception ex)
{
_logger.LogError(ex,ex.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to get roles"
});
}
}
[HttpGet("{id}")]
public async Task<IActionResult> GetRoleByIdAsync(long id)
{
var role = await _rolesService.GetRoleByIdAsync(id);
_logger.LogInformation($"Role found successfully, {role.Id}");
if (role == null)
{
return NotFound(new BasicResponse()
{
Code = 404,
Message = "Role not found"
});
}
return Ok(role);
}
[HttpPost]
public async Task<IActionResult> CreateRoleAsync([FromBody] RoleDTO model)
{
try
{
var role = await _rolesService.CreateRoleAsync(model.Name, model.Description);
_logger.LogInformation($"Role created successfully, {role.Id}");
return CreatedAtAction(nameof(CreateRoleAsync), new { id = role.Id }, role);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to create role"
});
}
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateRoleAsync(long id, [FromBody] RoleDTO model)
{
try
{
if (await _rolesService.UpdateRoleAsync(id, model.Name, model.Description))
{
_logger.LogInformation($"Role updated successfully, {id}");
return Ok(new BasicResponse()
{
Code = 200,
Message = "Role updated successfully"
});
}
_logger.LogCritical($"Unknown error with role updating, {id}");
return StatusCode(418,new BasicResponse()
{
Code = 418,
Message = "Role not found"
});
}
catch (KeyNotFoundException)
{
_logger.LogError($"Role not found, {id} ");
return NotFound(new BasicResponse()
{
Code = 404,
Message = "Role not found"
});
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to update role"
});
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteRoleAsync(long id)
{
try
{
if (await _rolesService.DeleteRoleAsync(id))
{
_logger.LogInformation($"Role updated successfully, {id}");
return Ok(new BasicResponse()
{
Code = 200,
Message = "Role updated successfully"
});
}
_logger.LogCritical($"Unknown error with role deleting, RoleId {id}");
return StatusCode(418,new BasicResponse()
{
Code = 418,
Message = "Role not found"
});
}
catch (KeyNotFoundException)
{
_logger.LogError($"Role not found, {id} ");
return NotFound(new BasicResponse()
{
Code = 404,
Message = "Role not found"
});
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to delete role"
});
}
}
[HttpPost("{roleId}/rights/{rightId}")]
public async Task<IActionResult> AddRightToRoleAsync(long roleId, long rightId)
{
try
{
if (await _rolesService.AddRightToRoleAsync(roleId, rightId))
{
_logger.LogInformation($"Right added to role successfully, RoleId: {roleId}, RightId: {rightId}");
return Ok(new BasicResponse()
{
Code = 200,
Message = "Right added to role successfully"
});
}
_logger.LogCritical($"Unknown error with adding right to role, RoleId: {roleId}, RightId: {rightId}");
return StatusCode(418,new BasicResponse()
{
Code = 418,
Message = "Right not found for role"
});
}
catch(KeyNotFoundException e)
{
_logger.LogError(e, e.Message);
return NotFound(new BasicResponse()
{
Code = 404,
Message = "Right not found for role"
});
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to add right to role"
});
}
}
[HttpDelete("{roleId}/rights/{rightId}")]
public async Task<IActionResult> RemoveRightFromRoleAsync(long roleId, long rightId)
{
try
{
if (await _rolesService.RemoveRightFromRoleAsync(roleId, rightId))
{
_logger.LogInformation($"Right removed from role successfully, RoleId: {roleId}, RightId: {rightId}");
return Ok(new BasicResponse()
{
Code = 200,
Message = "Right removed from role successfully"
});
}
_logger.LogCritical($"Unknown error with removing right from role, RoleId: {roleId}, RightId: {rightId}");
return StatusCode(418, new BasicResponse()
{
Code = 418,
Message = "Right not found right for role"
});
}
catch (KeyNotFoundException e)
{
_logger.LogError(e, e.Message);
return NotFound(new BasicResponse()
{
Code = 404,
Message = "Right not found for role"
});
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
return StatusCode(500, new BasicResponse()
{
Code = 500,
Message = "Failed to remove right from role"
});
}
}
#endregion
}

View File

@@ -1,184 +0,0 @@
using AutoMapper;
using GamificationService.Exceptions.Services.ProfileService;
using GamificationService.Models.Database;
using GamificationService.Models.DTO;
using GamificationService.Services.UsersProfile;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace GamificationService.Controllers;
[ApiController]
[Authorize(Policy = "User")]
[Route("api/[controller]")]
public class UserProfileController : ControllerBase
{
private readonly IUserProfileService _userProfilesService;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<UserProfileController> _logger;
private readonly IMapper _mapper;
public UserProfileController(IUserProfileService userProfilesService, UserManager<ApplicationUser> userManager, ILogger<UserProfileController> logger, IMapper mapper)
{
_userProfilesService = userProfilesService;
_userManager = userManager;
_logger = logger;
_mapper = mapper;
}
/// <summary>
/// Gets a user profile by its ID.
/// </summary>
/// <param name="username">The username of the user profile's owner.</param>
/// <returns>An <see cref="UserProfileDTO"/> containing the user profile DTO if found, or a 404 Not Found if not found.</returns>
/// <response code="200">Returns the user profile DTO</response>
/// <response code="404">If the user profile is not found</response>
[HttpGet("user/{username}")]
public async Task<IActionResult> GetUserProfileByUsername(string username)
{
try
{
var user = (await _userManager.FindByNameAsync(username));
if (user == null)
{
return NotFound();
}
var userProfile = _userProfilesService.GetUserProfileByUserId(user.Id);
return Ok(_mapper.Map<UserProfileDTO>(userProfile));
}
catch (ProfileNotFoundException)
{
return NotFound();
}
}
/// <summary>
/// Gets a user profile by its ID.
/// </summary>
/// <param name="id">The ID of the user profile.</param>
/// <returns>An <see cref="UserProfileDTO"/> containing the user profile DTO if found, or a 404 Not Found if not found.</returns>
/// <response code="200">Returns the user profile DTO</response>
/// <response code="404">If the user profile is not found</response>
[HttpGet("{id}")]
public IActionResult GetUserProfileById(long id)
{
try
{
var userProfile = _userProfilesService.GetUserProfileById(id);
return Ok(_mapper.Map<UserProfileDTO>(userProfile));
}
catch (ProfileNotFoundException)
{
return NotFound();
}
}
/// <summary>
/// Adds a new user profile.
/// </summary>
/// <param name="username">The username of the user.</param>
/// <param name="model">The user profile model.</param>
/// <returns>A <see cref="UserProfileDTO"/> containing the created user profile if successful, or a 500 Internal Server Error if not successful.</returns>
/// <response code="200">Returns the created user profile</response>
/// <response code="404">If the user is not found</response>
[HttpPost("user/{username}")]
[Authorize(Policy = "Admin")]
public async Task<IActionResult> AddUserProfile(string username, [FromBody] UserProfileCreateDTO model)
{
var user = (await _userManager.FindByNameAsync(username));
if (user == null)
{
return NotFound();
}
try
{
var userProfile = await _userProfilesService.AddUserProfile(user.Id, model);
return Ok(_mapper.Map<UserProfileDTO>(userProfile));
}
catch (ProfileNotFoundException)
{
return NotFound();
}
}
/// <summary>
/// Update user profile for the logged in user.
/// </summary>
/// <param name="model">The user profile model.</param>
/// <returns>A <see cref="UserProfileDTO"/> containing the updated user profile if successful, or a 500 Internal Server Error if not successful.</returns>
/// <response code="200">Returns the updated user profile</response>
/// <response code="404">If the user profile is not found</response>
[HttpPut]
public async Task<IActionResult> UpdateUserProfile([FromBody] UserProfileCreateDTO model)
{
string username = User.Claims.First(c => c.Type == "username").Value;
long userId = (await _userManager.FindByNameAsync(username))!.Id;
try
{
bool result = await _userProfilesService.UpdateUserProfileByUserId(userId, model);
return Ok(result);
}
catch (ProfileNotFoundException)
{
return NotFound();
}
}
/// <summary>
/// Updates an existing user profile.
/// </summary>
/// <param name="username">The username of the user.</param>
/// <param name="model">The user profile model.</param>
/// <returns>A <see cref="UserProfileDTO"/> containing the updated user profile if successful, or a 500 Internal Server Error if not successful.</returns>
/// <response code="200">Returns the updated user profile</response>
/// <response code="404">If the user profile is not found</response>
[HttpPut]
[Authorize(Policy = "Admin")]
[Route("user/{userId}")]
public async Task<IActionResult> UpdateUserProfileByUsername(string username, [FromBody] UserProfileCreateDTO model)
{
var user = (await _userManager.FindByNameAsync(username));
if (user == null)
{
return NotFound();
}
try
{
bool result = await _userProfilesService.UpdateUserProfileByUserId(user.Id, model);
return Ok(result);
}
catch (ProfileNotFoundException)
{
return NotFound();
}
}
/// <summary>
/// Deletes an existing user profile.
/// </summary>
/// <param name="id">The ID of the user profile to delete.</param>
/// <returns>A <see cref="bool"/></returns>
/// <response code="200">Returns true.</response>
/// <response code="404">If the user profile is not found</response>
[HttpDelete("{id}")]
[Authorize(Policy = "Admin")]
public IActionResult DeleteUserProfile(long id)
{
try
{
_userProfilesService.DeleteUserProfile(id);
return Ok();
}
catch (ProfileNotFoundException)
{
return NotFound();
}
}
}

View File

@@ -1,32 +1,16 @@
using GamificationService.Models.Database;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace GamificationService.Database;
public class ApplicationContext : IdentityDbContext<ApplicationUser, ApplicationRole, long>
public class ApplicationContext : DbContext
{
public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
{
}
public DbSet<Right> Rights { get; set; }
public DbSet<RefreshToken> RefreshTokens { get; set; }
public DbSet<ApplicationUser> Users { get; set; }
public DbSet<ApplicationRole> Roles { get; set; }
public DbSet<UserRole> UserRoles { get; set; }
public DbSet<RoleRight> RoleRights { get; set; }
public DbSet<UserProfile> UserProfiles { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserRole>()
.HasKey(ur => new { ur.UserId, ur.RoleId });
modelBuilder.Entity<RoleRight>()
.HasKey(rr => new { rr.RoleId, rr.RightId });
}
}

View File

@@ -1,54 +1,5 @@
using GamificationService.Models.Database;
using GamificationService.Services.CurrentUsers;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace GamificationService.Database.Extensions;
public static class ChangeTrackerExtensions
{
public static void SetAuditProperties(this ChangeTracker changeTracker, ICurrentUserService currentUserService)
{
changeTracker.DetectChanges();
IEnumerable<EntityEntry> entities =
changeTracker
.Entries()
.Where(t => t.Entity is AuditableEntity &&
(
t.State == EntityState.Deleted
|| t.State == EntityState.Added
|| t.State == EntityState.Modified
));
if (entities.Any())
{
DateTimeOffset timestamp = DateTimeOffset.UtcNow;
string user = currentUserService.GetCurrentUser().Login ?? "Unknown";
foreach (EntityEntry entry in entities)
{
AuditableEntity entity = (AuditableEntity)entry.Entity;
switch (entry.State)
{
case EntityState.Added:
entity.CreatedOn = timestamp;
entity.CreatedBy = user;
entity.UpdatedOn = timestamp;
entity.UpdatedBy = user;
break;
case EntityState.Modified:
entity.UpdatedOn = timestamp;
entity.UpdatedBy = user;
break;
case EntityState.Deleted:
entity.UpdatedOn = timestamp;
entity.UpdatedBy = user;
entry.State = EntityState.Deleted;
break;
}
}
}
}
}

View File

@@ -1,4 +1,3 @@
using GamificationService.Models.Database;
using Microsoft.EntityFrameworkCore.Storage;
namespace GamificationService.Database.Repositories;
@@ -8,12 +7,6 @@ public class UnitOfWork : IDisposable
#region fields
private ApplicationContext _context;
private GenericRepository<UserProfile> _userProfileRepository;
private GenericRepository<ApplicationRole> _roleRepository;
private GenericRepository<Right?> _rightRepository;
private GenericRepository<RefreshToken> _refreshTokenRepository;
private GenericRepository<RoleRight> _roleRightRepository;
private GenericRepository<UserRole> _userRoleRepository;
#endregion
@@ -28,78 +21,6 @@ public class UnitOfWork : IDisposable
#region Properties
public GenericRepository<UserProfile> UserProfileRepository
{
get
{
if (this._userProfileRepository == null)
{
this._userProfileRepository = new GenericRepository<UserProfile>(_context);
}
return _userProfileRepository;
}
}
public GenericRepository<ApplicationRole> RoleRepository
{
get
{
if (this._roleRepository == null)
{
this._roleRepository = new GenericRepository<ApplicationRole>(_context);
}
return _roleRepository;
}
}
public GenericRepository<Right?> RightRepository
{
get
{
if (this._rightRepository == null)
{
this._rightRepository = new GenericRepository<Right?>(_context);
}
return _rightRepository;
}
}
public GenericRepository<RefreshToken> RefreshTokenRepository
{
get
{
if (this._refreshTokenRepository == null)
{
this._refreshTokenRepository = new GenericRepository<RefreshToken>(_context);
}
return _refreshTokenRepository;
}
}
public GenericRepository<RoleRight> RoleRightRepository
{
get
{
if (this._roleRightRepository == null)
{
this._roleRightRepository = new GenericRepository<RoleRight>(_context);
}
return _roleRightRepository;
}
}
public GenericRepository<UserRole> UserRoleRepository
{
get
{
if (this._userRoleRepository == null)
{
this._userRoleRepository = new GenericRepository<UserRole>(_context);
}
return _userRoleRepository;
}
}
#endregion
public bool Save()

View File

@@ -1,26 +0,0 @@
namespace GamificationService.Exceptions.Services.AuthService;
/// <summary>
/// Represents an exception related to authentication service operations.
/// </summary>
public class AuthServiceException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="AuthServiceException"/> class.
/// </summary>
public AuthServiceException() : base() { }
/// <summary>
/// Initializes a new instance of the <see cref="AuthServiceException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public AuthServiceException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="AuthServiceException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public AuthServiceException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -1,26 +0,0 @@
namespace GamificationService.Exceptions.Services.JwtService;
/// <summary>
/// Represents an exception related to jwt token service operations.
/// </summary>
public class GenerateRefreshTokenException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="enerateRefreshTokenException"/> class.
/// </summary>
public GenerateRefreshTokenException() : base() { }
/// <summary>
/// Initializes a new instance of the <see cref="GenerateRefreshTokenException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public GenerateRefreshTokenException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="GenerateRefreshTokenException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public GenerateRefreshTokenException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -1,26 +0,0 @@
namespace GamificationService.Exceptions.Services.JwtService;
/// <summary>
/// Represents an exception related to jwt token service operations.
/// </summary>
public class JwtServiceException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="JwtServiceException"/> class.
/// </summary>
public JwtServiceException() : base() { }
/// <summary>
/// Initializes a new instance of the <see cref="JwtServiceException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public JwtServiceException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="JwtServiceException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public JwtServiceException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -1,26 +0,0 @@
namespace GamificationService.Exceptions.Services.ProfileService;
/// <summary>
/// Represents an exception that occurs during profile creation operations.
/// </summary>
public class ProfileCreationException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ProfileCreationException"/> class.
/// </summary>
public ProfileCreationException() : base() { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileCreationException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public ProfileCreationException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileCreationException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public ProfileCreationException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -1,26 +0,0 @@
namespace GamificationService.Exceptions.Services.ProfileService;
/// <summary>
/// Represents an exception that occurs during profile deletion operations.
/// </summary>
public class ProfileDeletionException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ProfileDeletionException"/> class.
/// </summary>
public ProfileDeletionException() : base() { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileDeletionException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public ProfileDeletionException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileDeletionException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public ProfileDeletionException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -1,26 +0,0 @@
namespace GamificationService.Exceptions.Services.ProfileService;
/// <summary>
/// Represents an exception that occurs when a profile already exists.
/// </summary>
public class ProfileExistsException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ProfileExistsException"/> class.
/// </summary>
public ProfileExistsException() : base() { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileExistsException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public ProfileExistsException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileExistsException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public ProfileExistsException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -1,26 +0,0 @@
namespace GamificationService.Exceptions.Services.ProfileService;
/// <summary>
/// Represents an exception that occurs when a profile is not found.
/// </summary>
public class ProfileNotFoundException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ProfileNotFoundException"/> class.
/// </summary>
public ProfileNotFoundException() : base() { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileNotFoundException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public ProfileNotFoundException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileNotFoundException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public ProfileNotFoundException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -1,26 +0,0 @@
namespace GamificationService.Exceptions.Services.ProfileService;
/// <summary>
/// Represents an exception that occurs during profile update operations.
/// </summary>
public class ProfileUpdateException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ProfileUpdateException"/> class.
/// </summary>
public ProfileUpdateException() : base() { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileUpdateException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public ProfileUpdateException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="ProfileUpdateException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public ProfileUpdateException(string message, Exception innerException) : base(message, innerException) { }
}

View File

@@ -6,14 +6,6 @@ using GamificationService.Database;
using GamificationService.Database.Repositories;
using GamificationService.Logs;
using GamificationService.Mapper;
using GamificationService.Services.Cookies;
using GamificationService.Services.CurrentUsers;
using GamificationService.Services.JWT;
using GamificationService.Services.NotificationService;
using GamificationService.Services.Rights;
using GamificationService.Services.Roles;
using GamificationService.Utils;
using GamificationService.Utils.Factory;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
@@ -135,56 +127,6 @@ public static class UtilServicesExtensions
{
public static IServiceCollection AddUtilServices(this IServiceCollection services)
{
services.AddScoped<IJwtService, JwtService>();
services.AddScoped<ICookieService, CookieService>();
services.AddScoped<INotificationService,NotificationService>();
services.AddScoped<IRightsService,RightsService>();
services.AddScoped<IRolesService, RolesService>();
services.AddScoped<ICurrentUserService, CurrentUserService>();
return services;
}
}
public static class NotificationSettings
{
public static IServiceCollection AddPushNotifications(this IServiceCollection services, IConfiguration configuration)
{
var notificationSettings = configuration.GetSection("NotificationSettings");
var apiKey = notificationSettings["ApiKey"];
var token = notificationSettings["Token"];
var baseUrl = notificationSettings["Url"];
var projectId = notificationSettings["ProjectId"];
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(baseUrl);
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
services.AddSingleton(provider =>
{
var logger = provider.GetRequiredService<ILogger<PushNotificationsClient>>();
return new PushNotificationsClient(client, logger, token, projectId);
});
return services;
}
}
public static class EmailExtensions
{
public static IServiceCollection AddEmail(this IServiceCollection services, IConfiguration configuration)
{
var smtpSettings = configuration.GetSection("EmailSettings");
var host = smtpSettings["Host"] ?? "localhost";
var port = Convert.ToInt32(smtpSettings["Port"] ?? "25");
var username = smtpSettings["Username"] ?? "username";
var password = smtpSettings["Password"] ?? "password";
var email = smtpSettings["EmailFrom"] ?? "email";
services.AddScoped<SmtpClient>(sp => new SmtpClient(host)
{
Port = port,
Credentials = new NetworkCredential(username, password),
EnableSsl = true,
});
services.AddSingleton<EmailClient>();
return services;
}
}
@@ -193,8 +135,6 @@ public static class FactoryExtensions
{
public static IServiceCollection AddFactories(this IServiceCollection services)
{
services.AddSingleton<MailNotificationsFactory>();
services.AddSingleton<PushNotificationsFactory>();
return services;
}
}

View File

@@ -1,7 +1,4 @@
using AutoMapper;
using GamificationService.Models.Database;
using GamificationService.Models.DTO;
namespace GamificationService.Mapper;
@@ -9,43 +6,5 @@ public class MappingProfile : Profile
{
public MappingProfile()
{
#region UserProfileMapping
CreateMap<UserProfile, UserProfileDTO>()
.ForMember(x => x.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(x => x.UserId, opt => opt.MapFrom(src => src.UserId))
.ForMember(x => x.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(x => x.Surname, opt => opt.MapFrom(src => src.Surname))
.ForMember(x => x.Patronymic, opt => opt.MapFrom(src => src.Patronymic))
.ForMember(x => x.Birthdate, opt => opt.MapFrom(src => src.Birthdate))
.ForMember(x => x.Gender, opt => opt.MapFrom(src => src.Gender))
.ForMember(x => x.ContactEmail, opt => opt.MapFrom(src => src.ContactEmail))
.ForMember(x => x.ContactPhone, opt => opt.MapFrom(src => src.ContactPhone))
.ForMember(x => x.ProfilePicture, opt => opt.MapFrom(src => src.ProfilePicture));
CreateMap<UserProfileDTO, UserProfile>()
.ForMember(x => x.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(x => x.UserId, opt => opt.MapFrom(src => src.UserId))
.ForMember(x => x.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(x => x.Surname, opt => opt.MapFrom(src => src.Surname))
.ForMember(x => x.Patronymic, opt => opt.MapFrom(src => src.Patronymic))
.ForMember(x => x.Birthdate, opt => opt.MapFrom(src => src.Birthdate))
.ForMember(x => x.Gender, opt => opt.MapFrom(src => src.Gender))
.ForMember(x => x.ContactEmail, opt => opt.MapFrom(src => src.ContactEmail))
.ForMember(x => x.ContactPhone, opt => opt.MapFrom(src => src.ContactPhone))
.ForMember(x => x.ProfilePicture, opt => opt.MapFrom(src => src.ProfilePicture));
CreateMap<UserProfileCreateDTO, UserProfile>()
.ForMember(x => x.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(x => x.Surname, opt => opt.MapFrom(src => src.Surname))
.ForMember(x => x.Patronymic, opt => opt.MapFrom(src => src.Patronymic))
.ForMember(x => x.Birthdate, opt => opt.MapFrom(src => src.Birthdate))
.ForMember(x => x.Gender, opt => opt.MapFrom(src => src.Gender))
.ForMember(x => x.ContactEmail, opt => opt.MapFrom(src => src.ContactEmail))
.ForMember(x => x.ContactPhone, opt => opt.MapFrom(src => src.ContactPhone))
.ForMember(x => x.ProfilePicture, opt => opt.MapFrom(src => src.ProfilePicture));
#endregion
}
}

View File

@@ -1,21 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace GamificationService.Models.DTO;
public class AuthDTO
{
[Required]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Username must be between 3 and 50 characters")]
public string Username { get; set; } = null!;
[Required]
[EmailAddress(ErrorMessage = "Invalid email address")]
public string Email { get; set; } = null!;
[Required]
[StringLength(100, MinimumLength = 8, ErrorMessage = "Password must be between 8 and 100 characters")]
public string Password { get; set; } = null!;
[Required]
public bool RememberMe { get; set; }
}

View File

@@ -1,7 +0,0 @@
namespace GamificationService.Models.DTO;
public class DisableTwoFactorDTO
{
public int TwoFactorProvider { get; set; }
public string Code { get; set; }
}

View File

@@ -1,6 +0,0 @@
namespace GamificationService.Models.DTO;
public class EnableTwoFactorDTO
{
public int TwoFactorProvider { get; set; }
}

View File

@@ -1,11 +0,0 @@
using GamificationService.Models.Database;
namespace GamificationService.Models.DTO;
public class GetAllRightsResponse
{
public List<Right> Rights { get; set; }
public int TotalCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
}

View File

@@ -1,11 +0,0 @@
using GamificationService.Models.Database;
namespace GamificationService.Models.DTO;
public class GetAllRolesResponse
{
public List<ApplicationRole> Roles { get; set; }
public int TotalCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
}

View File

@@ -1,11 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace GamificationService.Models.DTO;
public class GetTwoFactorDTO
{
[Required]
public int TwoFactorProvider { get; set; }
[StringLength(50, MinimumLength = 3, ErrorMessage = "Username must be between 3 and 50 characters")]
public string? Username { get; set; } = null!;
}

View File

@@ -1,9 +0,0 @@
namespace GamificationService.Models.DTO;
public class LoginResultResponse
{
public bool? RequiresTwoFactorAuth { get; set; }
public bool Success { get; set; }
public RefreshTokenDTO? Token { get; set; }
public int? TwoFactorProvider { get; set; }
}

View File

@@ -1,7 +0,0 @@
namespace GamificationService.Models.DTO;
public class RefreshTokenDTO
{
public string AccessToken { get; set; } = null!;
public string RefreshToken { get; set; } = null!;
}

View File

@@ -1,7 +0,0 @@
namespace GamificationService.Models.DTO;
public class RightDTO
{
public string Name { get; set; }
public string Description { get; set; }
}

View File

@@ -1,7 +0,0 @@
namespace GamificationService.Models.DTO;
public class RoleDTO
{
public string Name { get; set; }
public string Description { get; set; }
}

View File

@@ -1,16 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace GamificationService.Models.DTO;
public class TwoFactorDTO
{
[Required]
public int TwoFactorProvider { get; set; }
[StringLength(50, MinimumLength = 3, ErrorMessage = "Username must be between 3 and 50 characters")]
public string? Username { get; set; } = null!;
[Required]
[StringLength(6, MinimumLength = 6, ErrorMessage = "Code must be 6 characters long")]
public string Code { get; set; } = null!;
public bool RememberMe { get; set; }
}

View File

@@ -1,33 +0,0 @@
using System.ComponentModel.DataAnnotations;
using GamificationService.Utils.Enums;
namespace GamificationService.Models.DTO;
public class UserProfileCreateDTO
{
[Required(ErrorMessage = "Name is required")]
[StringLength(100, ErrorMessage = "Name must be less than 100 characters")]
public string Name { get; set; } = null!;
[Required(ErrorMessage = "Surname is required")]
[StringLength(100, ErrorMessage = "Surname must be less than 100 characters")]
public string Surname { get; set; } = null!;
[StringLength(50, ErrorMessage = "Patronymic must be less than 50 characters")]
public string? Patronymic { get; set; }
[Required(ErrorMessage = "Birthdate is required")]
public DateTime Birthdate { get; set; }
[Required(ErrorMessage = "Gender is required")]
public Gender Gender { get; set; }
[EmailAddress(ErrorMessage = "Invalid email")]
public string? ContactEmail { get; set; }
[Phone(ErrorMessage = "Invalid contact phone number")]
public string? ContactPhone { get; set; }
[Url(ErrorMessage = "Invalid avatar url")]
public string? ProfilePicture { get; set; }
}

View File

@@ -1,37 +0,0 @@
using System.ComponentModel.DataAnnotations;
using GamificationService.Utils.Enums;
namespace GamificationService.Models.DTO;
public class UserProfileDTO
{
public long? Id { get; set; }
public long? UserId { get; set; }
[Required(ErrorMessage = "Name is required")]
[StringLength(100, ErrorMessage = "Name must be less than 100 characters")]
public string Name { get; set; } = null!;
[Required(ErrorMessage = "Surname is required")]
[StringLength(100, ErrorMessage = "Surname must be less than 100 characters")]
public string Surname { get; set; } = null!;
[StringLength(50, ErrorMessage = "Patronymic must be less than 50 characters")]
public string? Patronymic { get; set; }
[Required(ErrorMessage = "Birthdate is required")]
public DateTime Birthdate { get; set; }
[Required(ErrorMessage = "Gender is required")]
public Gender Gender { get; set; }
[EmailAddress(ErrorMessage = "Invalid email")]
public string? ContactEmail { get; set; }
[Phone(ErrorMessage = "Invalid contact phone number")]
public string? ContactPhone { get; set; }
[Url(ErrorMessage = "Invalid avatar url")]
public string? ProfilePicture { get; set; }
}

View File

@@ -1,15 +0,0 @@
using Microsoft.AspNetCore.Identity;
using StackExchange.Redis;
namespace GamificationService.Models.Database;
public class ApplicationRole : IdentityRole<long>
{
public ApplicationRole() : base() { }
public ApplicationRole(string roleName) : base(roleName) { }
public string? Description { get; set; }
public List<UserRole> UserRoles { get; set; } = new List<UserRole>();
public List<RoleRight> RoleRights { get; set; } = new List<RoleRight>();
}

View File

@@ -1,19 +0,0 @@
using System.ComponentModel.DataAnnotations;
using GamificationService.Utils;
using Microsoft.AspNetCore.Identity;
namespace GamificationService.Models.Database;
public class ApplicationUser : IdentityUser<long>
{
[Required(ErrorMessage = "Username is required")]
[StringLength(50, ErrorMessage = "Username must be less than 50 characters")]
public string Username { get; set; } = null!;
public bool TwoFactorEnabled { get; set; }
public string? TwoFactorSecret { get; set; }
public bool EmailConfirmed { get; set; }
public List<TwoFactorProvider> TwoFactorProviders { get; set; } = new List<TwoFactorProvider>();
public List<RefreshToken> RefreshTokens { get; set; } = new List<RefreshToken>();
public List<UserRole> UserRoles { get; set; } = new List<UserRole>();
}

View File

@@ -1,25 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace GamificationService.Models.Database;
public class RefreshToken
{
[Key]
public long Id { get; set; }
public long UserId { get; set; }
public ApplicationUser User { get; set; } = null!;
[Required]
public string Token { get; set; } = null!;
public DateTime Expires { get; set; }
public DateTime Created { get; set; }
public bool IsExpired => DateTime.UtcNow >= Expires;
public bool IsRevoked { get; set; }
public string? RevokedByIp { get; set; }
public DateTime? RevokedOn { get; set; }
public bool IsActive => !IsRevoked && !IsExpired;
}

View File

@@ -1,18 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace GamificationService.Models.Database;
public class Right
{
[Key]
public long Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; } = null!;
[StringLength(100)]
public string? Description { get; set; }
public List<RoleRight> RoleRights { get; set; } = new List<RoleRight>();
}

View File

@@ -1,10 +0,0 @@
namespace GamificationService.Models.Database;
public class RoleRight
{
public long RoleId { get; set; }
public ApplicationRole Role { get; set; } = null!;
public long RightId { get; set; }
public Right Right { get; set; } = null!;
}

View File

@@ -1,40 +0,0 @@
using System.ComponentModel.DataAnnotations;
using GamificationService.Utils.Enums;
namespace GamificationService.Models.Database;
public class UserProfile
{
[Key]
public long Id { get; set; }
[Required(ErrorMessage = "User is required")]
public long UserId { get; set; }
public ApplicationUser? User { get; set; }
[Required(ErrorMessage = "Name is required")]
[StringLength(100, ErrorMessage = "Name must be less than 100 characters")]
public string Name { get; set; } = null!;
[Required(ErrorMessage = "Surname is required")]
[StringLength(100, ErrorMessage = "Surname must be less than 100 characters")]
public string Surname { get; set; } = null!;
[StringLength(50, ErrorMessage = "Patronymic must be less than 50 characters")]
public string? Patronymic { get; set; }
[Required(ErrorMessage = "Gender is required")]
public Gender Gender { get; set; }
[Required(ErrorMessage = "Birthdate is required")]
public DateTime Birthdate { get; set; }
[EmailAddress(ErrorMessage = "Invalid email")]
public string? ContactEmail { get; set; }
[Phone(ErrorMessage = "Invalid contact phone number")]
public string? ContactPhone { get; set; }
[Url(ErrorMessage = "Invalid avatar url")]
public string? ProfilePicture { get; set; }
}

View File

@@ -1,10 +0,0 @@
namespace GamificationService.Models.Database;
public class UserRole
{
public long UserId { get; set; }
public ApplicationUser User { get; set; } = null!;
public long RoleId { get; set; }
public ApplicationRole Role { get; set; } = null!;
}

View File

@@ -1,6 +0,0 @@
namespace GamificationService.Models.Messages.UserProfiles;
public class CreateUserProfileRequest
{
}

View File

@@ -1,7 +0,0 @@
namespace GamificationService.Models.Messages.UserProfiles;
public class CreateUserProfileResponse
{
}

View File

@@ -1,6 +0,0 @@
namespace GamificationService.Models.Messages.UserProfiles;
public class UpdateUserProfileRequest
{
}

View File

@@ -1,6 +0,0 @@
namespace GamificationService.Models.Messages.UserProfiles;
public class UpdateUserProfileResponse
{
}

View File

@@ -1,7 +0,0 @@
namespace GamificationService.Models;
public class UserSession
{
public string? Login { get; set; }
public bool IsAuthenticated { get; set; }
}

View File

@@ -1,47 +0,0 @@
using GamificationService.Exceptions.UtilServices.Cookies;
namespace GamificationService.Services.Cookies;
public class CookieService : ICookieService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<CookieService> _logger;
private readonly IConfiguration _configuration;
public CookieService(IHttpContextAccessor httpContextAccessor, ILogger<CookieService> logger, IConfiguration configuration)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_configuration = configuration;
}
public Task<bool> SetCookie(string key, string value, CookieOptions options)
{
try
{
_logger.LogDebug("Adding cookie {CookieKey} with value {CookieValue}", key, value);
_httpContextAccessor.HttpContext.Response.Cookies.Append(key, value, options);
return Task.FromResult(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to add cookie {CookieKey}", key);
throw new SetCookiesException(ex.Message);
}
}
public async Task<bool> RemoveCookie(string key)
{
try
{
_logger.LogDebug("Deleting cookie {CookieKey}", key);
_httpContextAccessor.HttpContext.Response.Cookies.Delete(key);
return await Task.FromResult(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to delete cookie {CookieKey}", key);
throw new DeleteCookiesException(ex.Message);
}
}
}

View File

@@ -1,7 +0,0 @@
namespace GamificationService.Services.Cookies;
public interface ICookieService
{
Task<bool> SetCookie(string key, string value, CookieOptions options);
Task<bool> RemoveCookie(string key);
}

View File

@@ -1,26 +0,0 @@
using GamificationService.Models;
namespace GamificationService.Services.CurrentUsers;
public class CurrentUserService : ICurrentUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<ICurrentUserService> _logger;
public CurrentUserService(IHttpContextAccessor httpContextAccessor, ILogger<ICurrentUserService> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public UserSession GetCurrentUser()
{
UserSession currentUser = new UserSession
{
IsAuthenticated = _httpContextAccessor.HttpContext.User.Identity != null && _httpContextAccessor.HttpContext.User.Identity.IsAuthenticated,
Login = _httpContextAccessor.HttpContext.User.Identity.Name
};
_logger.LogDebug($"Current user extracted: {currentUser.Login}");
return currentUser;
}
}

View File

@@ -1,8 +0,0 @@
using GamificationService.Models;
namespace GamificationService.Services.CurrentUsers;
public interface ICurrentUserService
{
UserSession GetCurrentUser();
}

View File

@@ -1,13 +0,0 @@
using System.IdentityModel.Tokens.Jwt;
using GamificationService.Models.Database;
using GamificationService.Models.DTO;
namespace GamificationService.Services.JWT;
public interface IJwtService
{
string GenerateAccessToken(ApplicationUser user);
JwtSecurityToken ValidateAccessToken(string token);
Task<RefreshToken> GenerateRefreshTokenAsync(ApplicationUser user);
Task RevokeRefreshTokenAsync(long userId, string refreshToken, string remoteIpAddress);
}

View File

@@ -1,144 +0,0 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using GamificationService.Database.Repositories;
using GamificationService.Exceptions.Services.JwtService;
using GamificationService.Exceptions.UtilServices.JWT;
using GamificationService.Models.Database;
using GamificationService.Models.DTO;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
namespace GamificationService.Services.JWT;
public class JwtService : IJwtService
{
#region Fields
private readonly IConfiguration _configuration;
private readonly ILogger<JwtService> _logger;
private readonly UnitOfWork _unitOfWork;
#endregion
public JwtService(IConfiguration configuration, ILogger<JwtService> logger, UnitOfWork unitOfWork)
{
_configuration = configuration;
_logger = logger;
_unitOfWork = unitOfWork;
}
public string GenerateAccessToken(ApplicationUser user)
{
var jwtSettings = _configuration.GetSection("JwtSettings");
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"]));
var issuer = jwtSettings["Issuer"];
var audience = jwtSettings["Audience"];
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var userRoles = _unitOfWork.UserRoleRepository.Get()
.Where(ur => ur.UserId == user.Id)
.Select(ur => ur.Role)
.Include(rr => rr.RoleRights)
.ThenInclude(rr=>rr.Right)
.ToList();
foreach (var role in userRoles)
{
claims.Add(new Claim(ClaimTypes.Role, role.Name));
foreach (var right in role.RoleRights.Select(rr => rr.Right))
{
claims.Add(new Claim("Right", right.Name));
}
}
var expires = DateTime.UtcNow.AddMinutes(double.Parse(jwtSettings["AccessTokenExpirationMinutes"]));
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
expires: expires,
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
private string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
public JwtSecurityToken ValidateAccessToken(string token)
{
var jwtSettings = _configuration.GetSection("JwtSettings");
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"]));
var issuer = jwtSettings["Issuer"];
var audience = jwtSettings["Audience"];
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
SecurityToken validatedToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
return validatedToken as JwtSecurityToken;
}
public async Task<RefreshToken> GenerateRefreshTokenAsync(ApplicationUser user)
{
var dbRefreshToken = new RefreshToken
{
UserId = user.Id,
Token = GenerateRefreshToken(),
Expires = DateTime.UtcNow.AddDays(double.Parse(_configuration["JwtSettings:RefreshTokenExpirationDays"])),
Created = DateTime.UtcNow
};
await _unitOfWork.RefreshTokenRepository.InsertAsync(dbRefreshToken);
if (!await _unitOfWork.SaveAsync())
{
throw new GenerateRefreshTokenException("Failed to generate refresh token");
}
return dbRefreshToken;
}
public async Task RevokeRefreshTokenAsync(long userId, string refreshToken, string remoteIpAddress)
{
var token = await _unitOfWork.RefreshTokenRepository.Get()
.FirstOrDefaultAsync(x => x.UserId == userId && x.Token == refreshToken);
if (token != null)
{
token.IsRevoked = true;
token.RevokedByIp = remoteIpAddress;
token.RevokedOn = DateTime.UtcNow;
await _unitOfWork.SaveAsync();
}
}
}

View File

@@ -1,11 +0,0 @@
using System.Net.Mail;
using GamificationService.Models.Database;
using GamificationService.Utils;
namespace GamificationService.Services.NotificationService;
public interface INotificationService
{
public Task SendMailNotificationAsync(ApplicationUser user, Notification notification);
public Task SendPushNotificationAsync(ApplicationUser user, Notification notification);
}

View File

@@ -1,56 +0,0 @@
using System.Net.Mail;
using GamificationService.Models.Database;
using GamificationService.Utils;
using GamificationService.Utils.Factory;
namespace GamificationService.Services.NotificationService;
public class NotificationService : INotificationService
{
#region Services
private readonly EmailClient _emailClient;
private readonly ILogger<NotificationService> _logger;
private readonly PushNotificationsClient _pushNotificationsClient;
#endregion
#region Constructor
public NotificationService(EmailClient emailClient, PushNotificationsClient pushNotificationsClient, ILogger<NotificationService> logger)
{
_emailClient = emailClient;
_pushNotificationsClient = pushNotificationsClient;
_logger = logger;
}
#endregion
public async Task SendMailNotificationAsync(ApplicationUser user, Notification notification)
{
try
{
await _emailClient.SendEmail(((MailNotification)notification).ConvertToMailMessage(), user.Email);
}
catch (Exception e)
{
_logger.LogError(e,e.Message);
throw;
}
}
//TODO: Refactor, add reg.ru notifications
public async Task SendPushNotificationAsync(ApplicationUser user, Notification notification)
{
try
{
await _emailClient.SendEmail(((MailNotification)notification).ConvertToMailMessage(), user.Email);
}
catch (Exception e)
{
_logger.LogError(e,e.Message);
throw;
}
}
}

View File

@@ -1,12 +0,0 @@
using GamificationService.Models.Database;
namespace GamificationService.Services.Rights;
public interface IRightsService
{
Task<Right?> CreateRightAsync(string rightName, string description);
Task<bool> UpdateRightAsync(long rightId, string newRightName, string newDescription);
Task<bool> DeleteRightAsync(long rightId);
Task<Right?> GetRightByIdAsync(long rightId);
Task<(List<Right> Rights, int TotalCount)> GetAllRightsAsync(int pageNumber = 1, int pageSize = 10);
}

View File

@@ -1,103 +0,0 @@
using GamificationService.Database.Repositories;
using GamificationService.Models.Database;
using Microsoft.EntityFrameworkCore;
namespace GamificationService.Services.Rights;
public class RightsService : IRightsService
{
#region Fields
private readonly UnitOfWork _unitOfWork;
private readonly ILogger<IRightsService> _logger;
#endregion
#region Constructor
public RightsService(UnitOfWork unitOfWork, ILogger<IRightsService> logger)
{
_unitOfWork = unitOfWork;
_logger = logger;
}
#endregion
#region Methods
public async Task<Right?> CreateRightAsync(string rightName, string description)
{
var right = new Right
{
Name = rightName,
Description = description
};
await _unitOfWork.RightRepository.InsertAsync(right);
if (await _unitOfWork.SaveAsync())
{
return right;
}
throw new Exception($"Unable to create right for {rightName}");
}
public async Task<bool> UpdateRightAsync(long rightId, string newRightName, string newDescription)
{
var right = await _unitOfWork.RightRepository.GetByIDAsync(rightId);
if (right == null)
{
throw new KeyNotFoundException($"Right with ID {rightId} not found");
}
right.Name = newRightName;
right.Description = newDescription;
if (!await _unitOfWork.SaveAsync())
{
throw new Exception($"Unable to create right for {rightId}");
}
return true;
}
public async Task<bool> DeleteRightAsync(long rightId)
{
var right = await _unitOfWork.RightRepository.GetByIDAsync(rightId);
if (right == null)
{
throw new KeyNotFoundException($"Right with ID {rightId} not found");
}
_unitOfWork.RightRepository.Delete(right);
if (!await _unitOfWork.SaveAsync())
{
throw new Exception($"Unable to delete right for {rightId}");
}
return true;
}
public async Task<Right?> GetRightByIdAsync(long rightId)
{
return await _unitOfWork.RightRepository.GetByIDAsync(rightId);
}
public async Task<(List<Right> Rights, int TotalCount)> GetAllRightsAsync(int pageNumber = 1, int pageSize = 10)
{
var query = _unitOfWork.RightRepository.Get();
var totalItems = await query.CountAsync();
var pagedRights = await query
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (pagedRights, totalItems);
}
#endregion
}

View File

@@ -1,14 +0,0 @@
using GamificationService.Models.Database;
namespace GamificationService.Services.Roles;
public interface IRolesService
{
Task<ApplicationRole> CreateRoleAsync(string roleName, string description);
Task<bool> UpdateRoleAsync(long roleId, string newRoleName, string newDescription);
Task<bool> DeleteRoleAsync(long roleId);
Task<bool> AddRightToRoleAsync(long roleId, long rightId);
Task<bool> RemoveRightFromRoleAsync(long roleId, long rightId);
Task<ApplicationRole> GetRoleByIdAsync(long roleId);
Task<(List<ApplicationRole> Roles, int TotalCount)> GetAllRolesAsync(int pageNumber = 1, int pageSize = 10);
}

View File

@@ -1,162 +0,0 @@
using GamificationService.Database.Repositories;
using GamificationService.Models.Database;
using Microsoft.EntityFrameworkCore;
namespace GamificationService.Services.Roles;
public class RolesService : IRolesService
{
#region Services
private readonly ILogger<IRolesService> _logger;
private readonly UnitOfWork _unitOfWork;
#endregion
#region Constructor
public RolesService(ILogger<IRolesService> logger, UnitOfWork unitOfWork)
{
_logger = logger;
_unitOfWork = unitOfWork;
}
#endregion
#region Methods
//TODO: refactor database work, to be more beautiful
//ToDo: make better exception handling
public async Task<ApplicationRole> CreateRoleAsync(string roleName, string description)
{
var role = new ApplicationRole(roleName)
{
Description = description
};
await _unitOfWork.RoleRepository.InsertAsync(role);
if (await _unitOfWork.SaveAsync())
{
return role;
}
throw new Exception("Unable to create role");
}
public async Task<bool> UpdateRoleAsync(long roleId, string newRoleName, string newDescription)
{
var role = await _unitOfWork.RoleRepository.GetByIDAsync(roleId);
if (role == null)
{
throw new KeyNotFoundException($"Role with ID {roleId} not found");
}
role.Name = newRoleName;
role.Description = newDescription;
if (!await _unitOfWork.SaveAsync())
{
throw new Exception("Unable to create role");
}
return true;
}
public async Task<bool> DeleteRoleAsync(long roleId)
{
var role = await _unitOfWork.RoleRepository.GetByIDAsync(roleId);
if (role == null)
{
throw new KeyNotFoundException($"Role with ID {roleId} not found");
}
_unitOfWork.RoleRepository.Delete(role);
if (!await _unitOfWork.SaveAsync())
{
throw new Exception("Unable to delete role");
}
return true;
}
public async Task<bool> AddRightToRoleAsync(long roleId, long rightId)
{
var role = await _unitOfWork.RoleRepository.Get()
.Include(r => r.RoleRights)
.FirstOrDefaultAsync(r => r.Id == roleId);
var right = await _unitOfWork.RightRepository.GetByIDAsync(rightId);
if (role == null || right == null)
{
throw new KeyNotFoundException($"Role or Right not found");
}
var existingRight = role.RoleRights.FirstOrDefault(rr => rr.RightId == rightId);
if (existingRight == null)
{
role.RoleRights.Add(new RoleRight { RoleId = roleId, RightId = rightId });
if (!await _unitOfWork.SaveAsync())
{
throw new Exception("Unable to add role right");
}
}
return true;
}
public async Task<bool> RemoveRightFromRoleAsync(long roleId, long rightId)
{
var roleRight = await _unitOfWork.RoleRightRepository.Get()
.FirstOrDefaultAsync(rr => rr.RoleId == roleId && rr.RightId == rightId);
if (roleRight == null)
{
throw new KeyNotFoundException($"Right not found for role");
}
_unitOfWork.RoleRightRepository.Delete(roleRight);
if (!await _unitOfWork.SaveAsync())
{
throw new Exception("Unable to remove role right");
}
return true;
}
public async Task<ApplicationRole> GetRoleByIdAsync(long roleId)
{
try
{
return await _unitOfWork.RoleRepository.Get()
.Include(r => r.RoleRights)
.ThenInclude(rr => rr.Right)
.FirstOrDefaultAsync(r => r.Id == roleId);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
throw;
}
}
public async Task<(List<ApplicationRole> Roles, int TotalCount)> GetAllRolesAsync(int pageNumber = 1, int pageSize = 10)
{
var query = _unitOfWork.RoleRepository.Get()
.Include(r => r.RoleRights)
.ThenInclude(rr => rr.Right);
var totalItems = await query.CountAsync();
var pagedRoles = await query
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return (pagedRoles, totalItems);
}
#endregion
}

View File

@@ -1,15 +0,0 @@
using GamificationService.Models.Database;
using GamificationService.Models.DTO;
namespace GamificationService.Services.UsersProfile;
public interface IUserProfileService
{
public Task<UserProfileDTO> AddUserProfile(long userId, UserProfileCreateDTO userProfile);
public Task<UserProfile> AddUserProfile(UserProfile userProfile);
public UserProfile? GetUserProfileByUserId(long id);
public UserProfile? GetUserProfileById(long id);
public Task<bool> UpdateUserProfileByUserId(long userId, UserProfileCreateDTO userProfile);
public Task<bool> UpdateUserProfile(UserProfile userProfile);
public bool DeleteUserProfile(long id);
}

View File

@@ -1,121 +0,0 @@
using AutoMapper;
using GamificationService.Database.Repositories;
using GamificationService.Exceptions.Services.ProfileService;
using GamificationService.Models.Database;
using GamificationService.Models.DTO;
namespace GamificationService.Services.UsersProfile;
public class UserProfileService : IUserProfileService
{
private readonly UnitOfWork _unitOfWork;
# region Services
private readonly ILogger<UserProfileService> _logger;
private readonly IMapper _mapper;
#endregion
#region Constructor
public UserProfileService(UnitOfWork unitOfWork, ILogger<UserProfileService> logger, IMapper mapper)
{
_unitOfWork = unitOfWork;
_logger = logger;
_mapper = mapper;
}
#endregion
# region Methods
public async Task<UserProfileDTO> AddUserProfile(long userId, UserProfileCreateDTO userProfile)
{
UserProfile userProfileEntity = _mapper.Map<UserProfile>(userProfile);
userProfileEntity.UserId = userId;
return _mapper.Map<UserProfileDTO>(await AddUserProfile(userProfileEntity));
}
public async Task<UserProfile> AddUserProfile(UserProfile userProfile)
{
UserProfile userProfileEntity = userProfile;
// Make sure a user profile for the given user does not exist yet
if (_unitOfWork.UserProfileRepository.Get(x => x.UserId == userProfile.UserId).Any())
{
_logger.LogWarning("A user profile already exists for the given user id: {UserId}", userProfile.UserId);
throw new ProfileExistsException($"{userProfile.UserId}");
}
await _unitOfWork.UserProfileRepository.InsertAsync(userProfileEntity);
if (await _unitOfWork.SaveAsync())
{
_logger.LogInformation("User profile added for user id: {UserId}", userProfile.UserId);
return userProfileEntity;
}
_logger.LogError("Failed to add user profile for user id: {UserId}", userProfile.UserId);
throw new ProfileCreationException();
}
public UserProfile? GetUserProfileByUserId(long id)
{
return _unitOfWork.UserProfileRepository.Get(x => x.UserId == id).FirstOrDefault();
}
public UserProfile? GetUserProfileById(long id)
{
return _unitOfWork.UserProfileRepository.GetByID(id);
}
public async Task<bool> UpdateUserProfileByUserId(long userId, UserProfileCreateDTO userProfile)
{
var userProfileEntityUpdated = _mapper.Map<UserProfile>(userProfile);
var profile = _unitOfWork.UserProfileRepository
.Get(x => x.UserId == userId).FirstOrDefault() ?? throw new ProfileNotFoundException($"{userId}");
userProfileEntityUpdated.Id = profile.Id;
return await UpdateUserProfile(userProfileEntityUpdated);
}
public async Task<bool> UpdateUserProfile(UserProfile userProfile)
{
var userProfileEntityUpdated = userProfile;
var userProfileEntity = await _unitOfWork.UserProfileRepository.GetByIDAsync(userProfileEntityUpdated.Id);
if (userProfileEntity == null)
{
throw new ProfileNotFoundException($"{userProfileEntityUpdated.Id}");
}
_mapper.Map(userProfileEntityUpdated, userProfileEntity);
if (!await _unitOfWork.SaveAsync())
{
throw new ProfileUpdateException($"Failed to update user profile {userProfileEntityUpdated.Id}");
}
_logger.LogInformation("User profile updated for user id: {UserId}", userProfile.UserId);
return true;
}
public bool DeleteUserProfile(long id)
{
var profile = _unitOfWork.UserProfileRepository.GetByID(id);
if (profile == null)
{
throw new ProfileNotFoundException($"{id}");
}
_unitOfWork.UserProfileRepository.Delete(id);
if (_unitOfWork.Save())
{
_logger.LogInformation("User profile deleted: {UserId}", id);
return true;
}
throw new ProfileDeletionException($"Failed to delete user profile {id}");
}
#endregion
}

View File

@@ -1,38 +0,0 @@
using System.Net.Mail;
using GamificationService.Exceptions.UtilServices.Email;
namespace GamificationService.Utils;
public class EmailClient(SmtpClient smtpClient, string emailFrom, ILogger<EmailClient> logger)
{
#region Fields
private readonly string _emailFrom = emailFrom;
private readonly ILogger<EmailClient> _logger = logger;
#endregion
#region Methods
/// <summary>
/// Sends the email using the SmtpClient instance.
/// </summary>
/// <param name="email">Email to send.</param>
/// <exception cref="SendEmailException">If the SmtpClient instance fails to send the email.</exception>
/// <returns>Task that represents the asynchronous operation.</returns>
public async Task SendEmail(MailMessage email, string emailTo)
{
try
{
email.To.Add(new MailAddress(emailTo));
await smtpClient.SendMailAsync(email);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
throw new SendEmailException("Failed to send email", ex);
}
}
#endregion
}

View File

@@ -1,150 +0,0 @@
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using GamificationService.Exceptions.UtilServices.Api;
namespace GamificationService.Utils;
public class PushNotificationsClient
{
#region Fields
private readonly HttpClient _httpClient;
private readonly ILogger<PushNotificationsClient> _logger;
private readonly string _applicationToken;
private readonly string _projectId;
#endregion
#region Constructor
public PushNotificationsClient(HttpClient httpClient, ILogger<PushNotificationsClient> logger, string applicationToken, string projectId)
{
_httpClient = httpClient;
_logger = logger;
_applicationToken = applicationToken;
_projectId = projectId;
}
#endregion
#region Methods
public async Task SendPushNotification(PushNotification pushNotification)
{
try
{
var payload = new
{
message = new
{
token = _applicationToken,
notification = new
{
body = pushNotification.Message,
title = pushNotification.Title,
image = pushNotification.Image
},
android = new
{
notification = new
{
body = pushNotification.Message,
title = pushNotification.Title,
image = pushNotification.Image,
click_action = pushNotification.ClickAction,
click_action_type = pushNotification.ClickActionType
}
}
}
};
var jsonPayload = JsonSerializer.Serialize(payload);
var request = new HttpRequestMessage(HttpMethod.Post,$"/{_projectId}/messages")
{
Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
};
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == HttpStatusCode.BadRequest)
{
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogError($"Failed to send push notification. Status Code: {response.StatusCode}, Response: {responseContent}");
throw new BadRequestException($"Failed to send push notification: {response.StatusCode}");
}
else if (response.StatusCode == HttpStatusCode.Forbidden)
{
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogError($"Failed to send push notification: {response.StatusCode}, Response: {responseContent}");
throw new ForbiddenException($"Failed to send push notification: {response.StatusCode}");
}
else
{
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogError($"Failed to send push notification: {response.StatusCode}, Response: {responseContent}");
throw new Exception($"Failed to send push notification: {response.StatusCode}");
}
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
throw;
}
}
public async Task SendPushNotification(PushNotification pushNotification, string topic)
{
try
{
var payload = new
{
message = new
{
notification = new
{
body = pushNotification.Message,
title = pushNotification.Title,
image = pushNotification.Image
}
}
};
var jsonPayload = JsonSerializer.Serialize(payload);
var request = new HttpRequestMessage(HttpMethod.Post,$"/{_projectId}/topics/{topic}/publish")
{
Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
};
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == HttpStatusCode.BadRequest)
{
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogError($"Failed to send push notification. Status Code: {response.StatusCode}, Response: {responseContent}");
throw new BadRequestException($"Failed to send push notification: {response.StatusCode}");
}
else if (response.StatusCode == HttpStatusCode.Forbidden)
{
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogError($"Failed to send push notification: {response.StatusCode}, Response: {responseContent}");
throw new ForbiddenException($"Failed to send push notification: {response.StatusCode}");
}
else
{
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogError($"Failed to send push notification: {response.StatusCode}, Response: {responseContent}");
throw new Exception($"Failed to send push notification: {response.StatusCode}");
}
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
throw;
}
}
#endregion
}

View File

@@ -1,7 +0,0 @@
namespace GamificationService.Utils.Enums;
public enum Gender
{
Male,
Female
}

View File

@@ -1,8 +0,0 @@
namespace GamificationService.Utils.Enums
{
public enum InstructionTestScoreCalcMethod
{
AverageGrade,
MaxGrade
}
}

View File

@@ -1,9 +0,0 @@
namespace GamificationService.Utils;
public enum NotificationInformationType
{
AUTH,
INFO,
WARNING,
ERROR
}

View File

@@ -1,21 +0,0 @@
using System.Net.Mail;
namespace GamificationService.Utils.Factory;
public class MailNotificationsFactory
{
public static Notification CreateNotification(NotificationInformationType type,
string title,
string message,List<Attachment> attachments)
{
return new MailNotification(type, title, message, attachments);
}
public Notification CreateNotification(NotificationInformationType type,
string title,
string message)
{
return new MailNotification(type, title, message);
}
}

View File

@@ -1,28 +0,0 @@
namespace GamificationService.Utils.Factory;
public class PushNotificationsFactory
{
public Notification CreateNotification(NotificationInformationType type,
string title,
string message)
{
return new PushNotification(type, title, message);
}
public Notification CreateNotification(NotificationInformationType type,
string title,
string message,
string image)
{
return new PushNotification(type, title, message, image);
}
public Notification CreateNotification(NotificationInformationType type,
string title,
string message,
string? image,
string clickAction,
ClickActionType clickActionType)
{
return new PushNotification(type, title, message, image, clickAction, clickActionType);
}
}

View File

@@ -1,111 +0,0 @@
using System.Net.Mail;
namespace GamificationService.Utils;
public class MailNotification : Notification
{
#region Fields
private List<Attachment> _attachments;
#endregion
#region Properties
public List<Attachment> Attachments { get => _attachments; }
#endregion
#region Constructor
public MailNotification(NotificationInformationType type, string title, string message, List<Attachment> attachments) : base(type, title, message)
{
_attachments = attachments;
}
public MailNotification(NotificationInformationType type, string title, string message) : base(type, title, message)
{
}
#endregion
#region Methods
public MailMessage ConvertToMailMessage()
{
var mailMessage = new MailMessage
{
Subject = CreateTitle(),
Body = CreateBody(),
IsBodyHtml = true,
Priority = GetPriority()
};
if (_attachments != null)
{
mailMessage.Attachments.ToList().AddRange(_attachments);
}
return mailMessage;
}
#endregion
#region Private Methods
private string CreateTitle()
{
switch (Type)
{
case NotificationInformationType.AUTH:
return "Авторизация " + Title;
case NotificationInformationType.INFO:
return "Информация "+ Title;
case NotificationInformationType.WARNING:
return "Предупреждение "+ Title;
case NotificationInformationType.ERROR:
return "Ошибка "+ Title;
default:
return "Информация "+ Title;
}
}
private string CreateBody()
{
string formattedMessage;
switch (Type)
{
case NotificationInformationType.AUTH:
formattedMessage = "Вы успешно авторизовались.";
break;
case NotificationInformationType.INFO:
formattedMessage = "Это информационное сообщение.";
break;
case NotificationInformationType.WARNING:
formattedMessage = "Внимание! Обратите внимание на это предупреждение.";
break;
case NotificationInformationType.ERROR:
formattedMessage = "Произошла ошибка. Пожалуйста, проверьте детали.";
break;
default:
formattedMessage = "Сообщение не определено.";
break;
}
return $"<p style='font-size: 16px; line-height: 1.5; color: #555;'>{formattedMessage} {Message}</p>";
}
private MailPriority GetPriority()
{
switch (Type)
{
case NotificationInformationType.AUTH:
return MailPriority.High;
case NotificationInformationType.INFO:
return MailPriority.Normal;
case NotificationInformationType.WARNING:
return MailPriority.Low;
case NotificationInformationType.ERROR:
return MailPriority.High;
default:
return MailPriority.Normal;
}
}
#endregion
}

View File

@@ -1,33 +0,0 @@
using System.Net.Mail;
namespace GamificationService.Utils;
public abstract class Notification
{
#region Fields
private string _title;
private string _message;
private NotificationInformationType _type;
#endregion
#region Parameters
public string Title { get => _title;}
public string Message { get => _message; }
public NotificationInformationType Type { get => _type; }
#endregion
#region Constructor
public Notification(NotificationInformationType type, string title, string message)
{
_type = type;
_title = title;
_message = message;
}
#endregion
}

View File

@@ -1,40 +0,0 @@
namespace GamificationService.Utils;
public class PushNotification : Notification
{
#region Fields
private readonly string? _image;
private readonly string? _clickAction;
private readonly ClickActionType? _clickActionType;
#endregion
#region Properties
public string? Image { get => _image; }
public string? ClickAction { get => _clickAction; }
public int? ClickActionType { get => (int)_clickActionType; }
#endregion
#region Constructor
public PushNotification(NotificationInformationType type, string title, string message, string image, string clickAction, ClickActionType clickActionType) : base(type, title, message)
{
_image = image;
_clickAction = clickAction;
_clickActionType = clickActionType;
}
public PushNotification(NotificationInformationType type, string title, string message, string? image) : base(type, title, message)
{
_image = image;
}
public PushNotification(NotificationInformationType type, string title, string message) : base(type, title, message)
{
}
#endregion
}

View File

@@ -1,10 +0,0 @@
namespace GamificationService.Utils;
public enum TwoFactorProvider
{
NONE,
EMAIL,
PHONE,
PUSH,
AUTHENTICATOR
}